From 2b0bd53604563b51165bf96af08b31f00ad8c2da Mon Sep 17 00:00:00 2001 From: Lemma Dev <54662130+lemmadev@users.noreply.github.com> Date: Thu, 12 Nov 2020 22:06:09 +0530 Subject: [PATCH 001/103] Lemma bid adapter: Add user sync support (#5934) * lemmaBidAdapter.js Added lemma bid adapter file * lemmaBidAdapter.md Added lemma bid adapter md file * lemmaBidAdapter_spec.js Added lemma bid adapter test spec file * Update lemmaBidAdapter.js Fixed automated code review alert comparison between inconvertible types * Update lemmaBidAdapter.js Fixed review changes * Update lemmaBidAdapter.md Correct parameter value. * Update lemmaBidAdapter.js Lemma Bid Adapter - v3.0 compliance * Update lemmaBidAdapter_spec.js Lemma Bid Adapter - v3.0 compliance * Update lemmaBidAdapter.md Lemma Bid Adapter - v3.0 compliance * Update lemmaBidAdapter.js Added user sync support into bid adapter. * updated include modules file extension. updated include modules js file extension. * Update lemmaBidAdapter_spec.js Added unit test for user sync feature. * Update lemmaBidAdapter.js Fixed format error. * Update lemmaBidAdapter_spec.js Fixed format error and typo error. --- modules/lemmaBidAdapter.js | 34 ++++++++++++++++++++--- test/spec/modules/lemmaBidAdapter_spec.js | 34 +++++++++++++++++++++++ 2 files changed, 64 insertions(+), 4 deletions(-) diff --git a/modules/lemmaBidAdapter.js b/modules/lemmaBidAdapter.js index 1ad660e5916..5941802f97d 100644 --- a/modules/lemmaBidAdapter.js +++ b/modules/lemmaBidAdapter.js @@ -5,10 +5,13 @@ import { BANNER, VIDEO } from '../src/mediaTypes.js'; var BIDDER_CODE = 'lemma'; var LOG_WARN_PREFIX = 'LEMMA: '; var ENDPOINT = 'https://ads.lemmatechnologies.com/lemma/servad'; +var USER_SYNC = 'https://sync.lemmatechnologies.com/js/usersync.html?'; var DEFAULT_CURRENCY = 'USD'; var AUCTION_TYPE = 2; var DEFAULT_TMAX = 300; var DEFAULT_NET_REVENUE = false; +var pubId = 0; +var adunitId = 0; export var spec = { @@ -57,6 +60,29 @@ export var spec = { interpretResponse: (response, request) => { return parseRTBResponse(request, response.body); }, + getUserSyncs: (syncOptions, responses, gdprConsent, uspConsent) => { + let syncurl = USER_SYNC + 'pid=' + pubId; + + // Attaching GDPR Consent Params in UserSync url + if (gdprConsent) { + syncurl += '&gdpr=' + (gdprConsent.gdprApplies ? 1 : 0); + syncurl += '&gdpr_consent=' + encodeURIComponent(gdprConsent.consentString || ''); + } + + // CCPA + if (uspConsent) { + syncurl += '&us_privacy=' + encodeURIComponent(uspConsent); + } + + if (syncOptions.iframeEnabled) { + return [{ + type: 'iframe', + url: syncurl + }]; + } else { + utils.logWarn(LOG_WARN_PREFIX + 'Please enable iframe based user sync.'); + } + }, }; function _initConf(refererInfo) { @@ -167,8 +193,8 @@ function _getImpressionArray(request) { function endPointURL(request) { var params = request && request[0].params ? request[0].params : null; if (params) { - var pubId = params.pubId ? params.pubId : 0; - var adunitId = params.adunitId ? params.adunitId : 0; + pubId = params.pubId ? params.pubId : 0; + adunitId = params.adunitId ? params.adunitId : 0; return ENDPOINT + '?pid=' + pubId + '&aid=' + adunitId; } return null; @@ -183,7 +209,7 @@ function _getDomain(url) { function _getSiteObject(request, conf) { var params = request && request.params ? request.params : null; if (params) { - var pubId = params.pubId ? params.pubId : '0'; + pubId = params.pubId ? params.pubId : '0'; var siteId = params.siteId ? params.siteId : '0'; var appParams = params.app; if (!appParams) { @@ -204,7 +230,7 @@ function _getSiteObject(request, conf) { function _getAppObject(request) { var params = request && request.params ? request.params : null; if (params) { - var pubId = params.pubId ? params.pubId : 0; + pubId = params.pubId ? params.pubId : 0; var appParams = params.app; if (appParams) { return { diff --git a/test/spec/modules/lemmaBidAdapter_spec.js b/test/spec/modules/lemmaBidAdapter_spec.js index a236ac17d71..a00c25d126c 100644 --- a/test/spec/modules/lemmaBidAdapter_spec.js +++ b/test/spec/modules/lemmaBidAdapter_spec.js @@ -331,5 +331,39 @@ describe('lemmaBidAdapter', function() { }); }); }); + describe('getUserSyncs', function() { + const syncurl_iframe = 'https://sync.lemmatechnologies.com/js/usersync.html?pid=1001'; + let sandbox; + beforeEach(function() { + sandbox = sinon.sandbox.create(); + }); + afterEach(function() { + sandbox.restore(); + }); + + it('execute as per config', function() { + expect(spec.getUserSyncs({ iframeEnabled: true }, {}, undefined, undefined)).to.deep.equal([{ + type: 'iframe', url: syncurl_iframe + }]); + }); + + it('CCPA/USP', function() { + expect(spec.getUserSyncs({ iframeEnabled: true }, {}, undefined, '1NYN')).to.deep.equal([{ + type: 'iframe', url: `${syncurl_iframe}&us_privacy=1NYN` + }]); + }); + + it('GDPR', function() { + expect(spec.getUserSyncs({ iframeEnabled: true }, {}, { gdprApplies: true, consentString: 'foo' }, undefined)).to.deep.equal([{ + type: 'iframe', url: `${syncurl_iframe}&gdpr=1&gdpr_consent=foo` + }]); + expect(spec.getUserSyncs({ iframeEnabled: true }, {}, { gdprApplies: false, consentString: 'foo' }, undefined)).to.deep.equal([{ + type: 'iframe', url: `${syncurl_iframe}&gdpr=0&gdpr_consent=foo` + }]); + expect(spec.getUserSyncs({ iframeEnabled: true }, {}, { gdprApplies: true, consentString: undefined }, undefined)).to.deep.equal([{ + type: 'iframe', url: `${syncurl_iframe}&gdpr=1&gdpr_consent=` + }]); + }); + }); }); }); From 954acdb21f10fc14198f6c135364f81eee04d48c Mon Sep 17 00:00:00 2001 From: SKOCHERI <37454420+SKOCHERI@users.noreply.github.com> Date: Thu, 12 Nov 2020 10:33:13 -0800 Subject: [PATCH 002/103] ID Library (#5863) * ID Library * build fix * build fix * build fix * build fix * Fixing review comments * Fixing review comments (debounce, full body scan option * Fixing review comments (var declaration, strict check) * Fixing space Co-authored-by: skocheri --- modules/idLibrary.js | 243 ++++++++++++++++++++++++++++ modules/idLibrary.md | 24 +++ test/spec/modules/idLibrary_spec.js | 61 +++++++ 3 files changed, 328 insertions(+) create mode 100644 modules/idLibrary.js create mode 100644 modules/idLibrary.md create mode 100644 test/spec/modules/idLibrary_spec.js diff --git a/modules/idLibrary.js b/modules/idLibrary.js new file mode 100644 index 00000000000..ba3cc0b5efb --- /dev/null +++ b/modules/idLibrary.js @@ -0,0 +1,243 @@ +import {getGlobal} from '../src/prebidGlobal.js'; +import {ajax} from '../src/ajax.js'; +import {config} from '../src/config.js'; +import * as utils from '../src/utils.js'; +import MD5 from 'crypto-js/md5.js'; + +let email; +let conf; +const LOG_PRE_FIX = 'ID-Library: '; +const CONF_DEFAULT_OBSERVER_DEBOUNCE_MS = 250; +const CONF_DEFAULT_FULL_BODY_SCAN = true; +const OBSERVER_CONFIG = { + subtree: true, + attributes: true, + attributeOldValue: false, + childList: true, + attirbuteFilter: ['value'], + characterData: true, + characterDataOldValue: false +}; +const logInfo = createLogInfo(LOG_PRE_FIX); +const logError = createLogError(LOG_PRE_FIX); + +function createLogInfo(prefix) { + return function (...strings) { + utils.logInfo(prefix + ' ', ...strings); + } +} + +function createLogError(prefix) { + return function (...strings) { + utils.logError(prefix + ' ', ...strings); + } +} + +function getEmail(value) { + const matched = value.match(/([a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+)/gi); + if (!matched) { + return null; + } + logInfo('Email found' + matched[0]); + return matched[0]; +} + +function bodyAction(mutations, observer) { + logInfo('BODY observer on debounce called'); + // If the email is found in the input element, disconnect the observer + if (email) { + observer.disconnect(); + logInfo('Email is found, body observer disconnected'); + return; + } + + const body = document.body.innerHTML; + email = getEmail(body); + if (email !== null) { + logInfo(`Email obtained from the body ${email}`); + observer.disconnect(); + logInfo('Post data on email found in body'); + postData(); + } +} + +function targetAction(mutations, observer) { + logInfo('Target observer called'); + for (const mutation of mutations) { + for (const node of mutation.addedNodes) { + email = node.textContent; + + if (email) { + logInfo('Email obtained from the target ' + email); + observer.disconnect(); + logInfo('Post data on email found in target'); + postData(); + return; + } + } + } +} + +function addInputElementsElementListner(conf) { + logInfo('Adding input element listeners'); + const inputs = document.querySelectorAll('input[type=text], input[type=email]'); + + for (var i = 0; i < inputs.length; i++) { + logInfo(`Original Value in Input = ${inputs[i].value}`); + inputs[i].addEventListener('change', event => processInputChange(event)); + inputs[i].addEventListener('blur', event => processInputChange(event)); + } +} + +function removeInputElementsElementListner() { + logInfo('Removing input element listeners'); + const inputs = document.querySelectorAll('input[type=text], input[type=email]'); + + for (var i = 0; i < inputs.length; i++) { + inputs[i].removeEventListener('change', event => processInputChange(event)); + inputs[i].removeEventListener('blur', event => processInputChange(event)); + } +} + +function processInputChange(event) { + const value = event.target.value; + logInfo(`Modified Value of input ${event.target.value}`); + email = getEmail(value); + if (email !== null) { + logInfo('Email found in input ' + email); + postData(); + removeInputElementsElementListner(); + } +} + +function debounce(func, wait, immediate) { + var timeout; + return function () { + const context = this; + const args = arguments; + const later = function () { + timeout = null; + if (!immediate) func.apply(context, args); + }; + var callNow = immediate && !timeout; + clearTimeout(timeout); + if (callNow) { + func.apply(context, args); + } else { + logInfo('Debounce wait time ' + wait); + timeout = setTimeout(later, wait); + } + }; +}; + +function handleTargetElement() { + const targetObserver = new MutationObserver(debounce(targetAction, conf.debounce, false)); + + const targetElement = document.getElementById(conf.target); + if (targetElement) { + email = targetElement.innerText; + + if (!email) { + logInfo('Finding the email with observer'); + targetObserver.observe(targetElement, OBSERVER_CONFIG); + } else { + logInfo('Target found with target ' + email); + logInfo('Post data on email found in target with target'); + postData(); + } + } +} + +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); + logInfo('Post data on email found in the body without observer'); + postData(); + return; + } + addInputElementsElementListner(); + if (conf.fullscan === true) { + const bodyObserver = new MutationObserver(debounce(bodyAction, conf.debounce, false)); + bodyObserver.observe(document.body, OBSERVER_CONFIG); + } +} + +function doesInputElementsHaveEmail() { + const inputs = document.getElementsByTagName('input'); + + for (let index = 0; index < inputs.length; ++index) { + const curInput = inputs[index]; + email = getEmail(curInput.value); + if (email !== null) { + return true; + } + } + return false; +} + +function syncCallback() { + return { + success: function () { + logInfo('Data synced successfully.'); + }, + error: function () { + logInfo('Data sync failed.'); + } + } +} + +function postData() { + (getGlobal()).refreshUserIds(); + const userIds = (getGlobal()).getUserIds(); + if (Object.keys(userIds).length === 0) { + logInfo('No user ids'); + return; + } + logInfo('Users' + JSON.stringify(userIds)); + const syncPayload = {}; + syncPayload.hid = MD5(email).toString(); + syncPayload.uids = JSON.stringify(userIds); + const payloadString = JSON.stringify(syncPayload); + logInfo(payloadString); + ajax(conf.url, syncCallback(), payloadString, {method: 'POST', withCredentials: true}); +} + +function associateIds() { + if (window.MutationObserver || window.WebKitMutationObserver) { + if (conf.target) { + handleTargetElement(); + } else { + handleBodyElements(); + } + } +} + +export function setConfig(config) { + if (!config) { + logError('Required confirguration not provided'); + return; + } + if (!config.url) { + logError('The required url is not configured'); + return; + } + if (typeof config.debounce !== 'number') { + config.debounce = CONF_DEFAULT_OBSERVER_DEBOUNCE_MS; + logInfo('Set default observer debounce to ' + CONF_DEFAULT_OBSERVER_DEBOUNCE_MS); + } + if (typeof config.fullscan !== 'boolean') { + config.fullscan = CONF_DEFAULT_FULL_BODY_SCAN; + logInfo('Set default fullscan ' + CONF_DEFAULT_FULL_BODY_SCAN); + } + conf = config; + associateIds(); +} + +config.getConfig('idLibrary', config => setConfig(config.idLibrary)); diff --git a/modules/idLibrary.md b/modules/idLibrary.md new file mode 100644 index 00000000000..69b63dc466b --- /dev/null +++ b/modules/idLibrary.md @@ -0,0 +1,24 @@ +## ID Library Configuration Example + + +|Param |Required |Description | +|----------------|-------------------------------|-----------------------------| +|url |Yes | The url endpoint is used to post the hashed email and user ids. | +|target |No |It should contain the element id from which the email can be read. | +|debounce |No | Time in milliseconds before the email and ids are fetched | +|fullscan |No | Option to enable/disable full body scan to get email. By default the full body scan is enabled. | + +### Example +``` + pbjs.setConfig({ + idLibrary:{ + url: , + debounce: 250, + target: 'username', + fullscan: false + }, + }); +``` + + +``` diff --git a/test/spec/modules/idLibrary_spec.js b/test/spec/modules/idLibrary_spec.js new file mode 100644 index 00000000000..da61850f29b --- /dev/null +++ b/test/spec/modules/idLibrary_spec.js @@ -0,0 +1,61 @@ +import * as utils from 'src/utils.js'; +import * as idlibrary from 'modules/idLibrary.js'; + +var expect = require('chai').expect; + +describe('currency', function () { + let fakeCurrencyFileServer; + let sandbox; + let clock; + + let fn = sinon.spy(); + + beforeEach(function () { + fakeCurrencyFileServer = sinon.fakeServer.create(); + sinon.stub(utils, 'logInfo'); + sinon.stub(utils, 'logError'); + }); + + afterEach(function () { + utils.logInfo.restore(); + utils.logError.restore(); + fakeCurrencyFileServer.restore(); + idlibrary.setConfig({}); + }); + + describe('setConfig', function () { + beforeEach(function() { + sandbox = sinon.sandbox.create(); + clock = sinon.useFakeTimers(1046952000000); // 2003-03-06T12:00:00Z + }); + + afterEach(function () { + sandbox.restore(); + clock.restore(); + }); + + it('results when no config available', function () { + idlibrary.setConfig({}); + sinon.assert.called(utils.logError); + }); + it('results with config available', function () { + idlibrary.setConfig({ 'url': 'URL' }); + sinon.assert.called(utils.logInfo); + }); + it('results with config default debounce ', function () { + let config = { 'url': 'URL' } + idlibrary.setConfig(config); + expect(config.debounce).to.be.equal(250); + }); + it('results with config default fullscan ', function () { + let config = { 'url': 'URL' } + idlibrary.setConfig(config); + expect(config.fullscan).to.be.equal(true); + }); + it('results with config fullscan ', function () { + let config = { 'url': 'URL', 'fullscan': false } + idlibrary.setConfig(config); + expect(config.fullscan).to.be.equal(false); + }); + }); +}); From 17beca2fb045e29fe9dfcb722825eeffccf26852 Mon Sep 17 00:00:00 2001 From: mwehr-zeta <70167335+mwehr-zeta@users.noreply.github.com> Date: Thu, 12 Nov 2020 14:09:48 -0500 Subject: [PATCH 003/103] Zeta-Prebid (#5813) * Submit Zeta Adapter to Prebid * comments addressed * demo changes * additional polishing * additional polishing * Update hello_world.html * remove extraneous changes to hello_world.html * no, really this time * additional polishing * add unit test * stop tracking package-lock.json * stop tracking package-lock.json * stop tracking, please * Submit Zeta Adapter * refactor bidder_code value * fix markdown bidder code and include definerId in request --- modules/zetaBidAdapter.js | 161 +++++++++++++++++++++++ modules/zetaBidAdapter.md | 40 ++++++ test/spec/modules/zetaBidAdapter_spec.js | 79 +++++++++++ 3 files changed, 280 insertions(+) create mode 100644 modules/zetaBidAdapter.js create mode 100644 modules/zetaBidAdapter.md create mode 100644 test/spec/modules/zetaBidAdapter_spec.js diff --git a/modules/zetaBidAdapter.js b/modules/zetaBidAdapter.js new file mode 100644 index 00000000000..f60e8946799 --- /dev/null +++ b/modules/zetaBidAdapter.js @@ -0,0 +1,161 @@ +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; +const BIDDER_CODE = 'zeta_global'; +const ENDPOINT_URL = 'https://prebid.rfihub.com/prebid'; +const USER_SYNC_URL = 'https://p.rfihub.com/cm?pub=42770&in=1'; +const DEFAULT_CUR = 'USD'; +const TTL = 200; +const NET_REV = true; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function(bid) { + // check for all required bid fields + let isValid = !!( + bid && + bid.bidId && + bid.params && + bid.params.ip && + bid.params.user && + bid.params.user.buyeruid && + bid.params.definerId + ); + if (!isValid) { + utils.logWarn('Invalid bid request'); + } + return isValid; + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {Bids[]} validBidRequests - an array of bidRequest objects + * @param {BidderRequest} bidderRequest - master bidRequest object + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function(validBidRequests, bidderRequest) { + const secure = 1; // treat all requests as secure + const request = validBidRequests[0]; + const params = request.params; + let impData = { + id: request.bidId, + secure: secure, + banner: buildBanner(request) + }; + let isMobile = /(ios|ipod|ipad|iphone|android)/i.test(navigator.userAgent) ? 1 : 0; + let payload = { + id: bidderRequest.auctionId, + cur: [DEFAULT_CUR], + imp: [impData], + site: { + mobile: isMobile, + page: bidderRequest.refererInfo.referer + }, + device: { + ua: navigator.userAgent, + ip: params.ip + }, + user: { + buyeruid: params.user.buyeruid, + uid: params.user.uid + }, + ext: { + definerId: params.definerId + } + }; + if (params.test) { + payload.test = params.test; + } + if (request.gdprConsent) { + payload.regs = { + ext: { + gdpr: request.gdprConsent.gdprApplies === true ? 1 : 0 + } + }; + } + if (request.gdprConsent && request.gdprConsent.gdprApplies) { + payload.user = { + ext: { + consent: request.gdprConsent.consentString + } + }; + } + return { + method: 'POST', + url: ENDPOINT_URL, + data: JSON.stringify(payload), + }; + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @param bidRequest The payload from the server's response. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function(serverResponse, bidRequest) { + let bidResponse = []; + if (Object.keys(serverResponse.body).length !== 0) { + let zetaResponse = serverResponse.body; + let zetaBid = zetaResponse.seatbid[0].bid[0]; + let bid = { + requestId: zetaBid.impid, + cpm: zetaBid.price, + currency: zetaResponse.cur, + width: zetaBid.w, + height: zetaBid.h, + ad: zetaBid.adm, + ttl: TTL, + creativeId: zetaBid.crid, + netRevenue: NET_REV + }; + bidResponse.push(bid); + } + return bidResponse; + }, + + /** + * Register the user sync pixels which should be dropped after the auction. + * + * @param {SyncOptions} syncOptions Which user syncs are allowed? + * @param {ServerResponse[]} serverResponses List of server's responses. + * @param gdprConsent The GDPR consent parameters + * @param uspConsent The USP consent parameters + * @return {UserSync[]} The user syncs which should be dropped. + */ + getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) { + const syncs = []; + if (syncOptions.iframeEnabled) { + syncs.push({ + type: 'iframe', + url: USER_SYNC_URL + }); + } + return syncs; + } +} + +function buildBanner(request) { + let sizes = request.sizes; + if (request.mediaTypes && + request.mediaTypes.banner && + request.mediaTypes.banner.sizes) { + sizes = request.mediaTypes.banner.sizes; + } + return { + w: sizes[0][0], + h: sizes[0][1] + }; +} + +registerBidder(spec); diff --git a/modules/zetaBidAdapter.md b/modules/zetaBidAdapter.md new file mode 100644 index 00000000000..ce19b831d4d --- /dev/null +++ b/modules/zetaBidAdapter.md @@ -0,0 +1,40 @@ +# Overview + +``` +Module Name: Zeta Bidder Adapter +Module Type: Bidder Adapter +Maintainer: DL-ZetaDSP-Supply-Engineering@zetaglobal.com +``` + +# Description + +Module that connects to Zeta's demand sources + +# Test Parameters +``` + var adUnits = [ + { + mediaTypes: { + banner: { + sizes: [[300, 250]], // a display size + } + }, + bids: [ + { + bidder: 'zeta_global', + bidId: 12345, + params: { + placement: 12345, + user: { + uid: 12345, + buyeruid: 12345 + }, + ip: '111.222.33.44', + definerId: 1, + test: 1 + } + } + ] + } + ]; +``` diff --git a/test/spec/modules/zetaBidAdapter_spec.js b/test/spec/modules/zetaBidAdapter_spec.js new file mode 100644 index 00000000000..0d11614c926 --- /dev/null +++ b/test/spec/modules/zetaBidAdapter_spec.js @@ -0,0 +1,79 @@ +import { spec } from '../../../modules/zetaBidAdapter.js' + +describe('Zeta Bid Adapter', function() { + const bannerRequest = [{ + bidId: 12345, + auctionId: 67890, + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, + refererInfo: { + referer: 'zetaglobal.com' + }, + params: { + placement: 12345, + user: { + uid: 12345, + buyeruid: 12345 + }, + ip: '111.222.33.44', + definerId: 1, + test: 1 + } + }]; + + 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 = request.data; + expect(payload).to.not.be.empty; + }); + + const responseBody = { + id: '12345', + seatbid: [ + { + bid: [ + { + id: 'auctionId', + impid: 'impId', + price: 0.0, + adm: 'adMarkup', + crid: 'creativeId', + h: 250, + w: 300 + } + ] + } + ], + cur: 'USD' + }; + + it('Test the response parsing function', function () { + const receivedBid = responseBody.seatbid[0].bid[0]; + const response = {}; + response.body = responseBody; + + const bidResponse = spec.interpretResponse(response, null); + expect(bidResponse).to.not.be.empty; + + const bid = bidResponse[0]; + expect(bid).to.not.be.empty; + expect(bid.ad).to.equal(receivedBid.adm); + expect(bid.cpm).to.equal(receivedBid.price); + expect(bid.height).to.equal(receivedBid.h); + expect(bid.width).to.equal(receivedBid.w); + expect(bid.requestId).to.equal(receivedBid.impid); + }); +}); From d5c2c8fb70df5111aeff22eb9902829f273e7085 Mon Sep 17 00:00:00 2001 From: robertrmartinez Date: Thu, 12 Nov 2020 12:36:28 -0800 Subject: [PATCH 004/103] Prebid 4.16.0 Release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8dc3535cc21..c61e97d0adf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.16.0-pre", + "version": "4.16.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From ef7fb8673535d3f2718248ca75702472f5f9899f Mon Sep 17 00:00:00 2001 From: robertrmartinez Date: Thu, 12 Nov 2020 13:08:10 -0800 Subject: [PATCH 005/103] Increment pre version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c61e97d0adf..4b62de09e1f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.16.0", + "version": "4.17.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 09468bf036ad44a28de867f1baf8a81e90b03b80 Mon Sep 17 00:00:00 2001 From: Karim Mourra Date: Fri, 13 Nov 2020 10:49:46 -0500 Subject: [PATCH 006/103] [AD-1043] JW Player RTD - add targeting info to bid.rtd (#5950) * adds targeting to rtd * updates tmg module * updates unit tests * shortens realTimeData * updates grid adapter for rtd Co-authored-by: karimJWP --- modules/gridBidAdapter.js | 3 +- modules/jwplayerRtdProvider.js | 11 +- modules/jwplayerRtdProvider.md | 14 ++- test/spec/modules/gridBidAdapter_spec.js | 10 +- test/spec/modules/jwplayerRtdProvider_spec.js | 115 +++++++++++++++--- 5 files changed, 124 insertions(+), 29 deletions(-) diff --git a/modules/gridBidAdapter.js b/modules/gridBidAdapter.js index 5436a18c6cb..db1402ea9ad 100644 --- a/modules/gridBidAdapter.js +++ b/modules/gridBidAdapter.js @@ -72,11 +72,12 @@ export const spec = { if (!userId) { userId = bid.userId; } - const {params: {uid, keywords, bidFloor}, mediaTypes, bidId, adUnitCode, jwTargeting} = bid; + const {params: {uid, keywords, bidFloor}, mediaTypes, bidId, adUnitCode, rtd} = bid; bidsMap[bidId] = bid; if (!pageKeywords && !utils.isEmpty(keywords)) { pageKeywords = utils.transformBidderParamKeywords(keywords); } + const jwTargeting = rtd && rtd.jwplayer && rtd.jwplayer.targeting; if (jwTargeting) { if (!jwpseg && jwTargeting.segments) { jwpseg = jwTargeting.segments; diff --git a/modules/jwplayerRtdProvider.js b/modules/jwplayerRtdProvider.js index 17cb978aea3..197c3c192c8 100644 --- a/modules/jwplayerRtdProvider.js +++ b/modules/jwplayerRtdProvider.js @@ -247,9 +247,14 @@ function addTargetingToBids(bids, targeting) { return; } - bids.forEach(bid => { - bid.jwTargeting = targeting; - }); + bids.forEach(bid => addTargetingToBid(bid, targeting)); +} + +export function addTargetingToBid(bid, targeting) { + const rtd = bid.rtd || {}; + const jwRtd = {}; + jwRtd[SUBMODULE_NAME] = Object.assign({}, rtd[SUBMODULE_NAME], { targeting }); + bid.rtd = Object.assign({}, rtd, jwRtd); } function getPlayer(playerID) { diff --git a/modules/jwplayerRtdProvider.md b/modules/jwplayerRtdProvider.md index 3c83b6f521c..ae09277979a 100644 --- a/modules/jwplayerRtdProvider.md +++ b/modules/jwplayerRtdProvider.md @@ -86,11 +86,15 @@ Each bid for which targeting information was found will conform to the following adUnitCode: 'xyz', bidId: 'abc', ..., - jwTargeting: { - segments: ['123', '456'], - content: { - id: 'jw_abc123' - } + rtd: { + jwplayer: { + targeting: { + segments: ['123', '456'], + content: { + id: 'jw_abc123' + } + } + } } } ``` diff --git a/test/spec/modules/gridBidAdapter_spec.js b/test/spec/modules/gridBidAdapter_spec.js index 9cd7d100318..640bfda1fee 100644 --- a/test/spec/modules/gridBidAdapter_spec.js +++ b/test/spec/modules/gridBidAdapter_spec.js @@ -411,9 +411,13 @@ describe('TheMediaGrid Adapter', function () { const jsSegments = ['test_seg_1', 'test_seg_2']; const bidRequestsWithUserIds = bidRequests.map((bid) => { return Object.assign({ - jwTargeting: { - segments: jsSegments, - content: jsContent + rtd: { + jwplayer: { + targeting: { + segments: jsSegments, + content: jsContent + } + } } }, bid); }); diff --git a/test/spec/modules/jwplayerRtdProvider_spec.js b/test/spec/modules/jwplayerRtdProvider_spec.js index 6e0fd8eb8d7..48b432b6bb4 100644 --- a/test/spec/modules/jwplayerRtdProvider_spec.js +++ b/test/spec/modules/jwplayerRtdProvider_spec.js @@ -1,5 +1,5 @@ import { fetchTargetingForMediaId, getVatFromCache, extractPublisherParams, - formatTargetingResponse, getVatFromPlayer, enrichAdUnits, + formatTargetingResponse, getVatFromPlayer, enrichAdUnits, addTargetingToBid, fetchTargetingInformation, jwplayerSubmodule } from 'modules/jwplayerRtdProvider.js'; import { server } from 'test/mocks/xhr.js'; @@ -252,7 +252,7 @@ describe('jwplayerRtdProvider', function() { }; jwplayerSubmodule.getBidRequestData({ adUnits: [adUnit] }, bidRequestSpy); expect(bidRequestSpy.calledOnce).to.be.true; - expect(bid).to.have.deep.property('jwTargeting', expectedTargeting); + expect(bid.rtd.jwplayer).to.have.deep.property('targeting', expectedTargeting); fakeServer.respond(); expect(bidRequestSpy.calledOnce).to.be.true; }); @@ -313,8 +313,8 @@ describe('jwplayerRtdProvider', function() { enrichAdUnits([adUnit]); const bid1 = bids[0]; const bid2 = bids[1]; - expect(bid1).to.not.have.property('jwTargeting'); - expect(bid2).to.not.have.property('jwTargeting'); + expect(bid1).to.not.have.property('rtd'); + expect(bid2).to.not.have.property('rtd'); const request = fakeServer.requests[0]; request.respond( @@ -330,8 +330,8 @@ describe('jwplayerRtdProvider', function() { }) ); - expect(bid1).to.have.deep.property('jwTargeting', expectedTargetingForSuccess); - expect(bid2).to.have.deep.property('jwTargeting', expectedTargetingForSuccess); + expect(bid1.rtd.jwplayer).to.have.deep.property('targeting', expectedTargetingForSuccess); + expect(bid2.rtd.jwplayer).to.have.deep.property('targeting', expectedTargetingForSuccess); }); it('immediately adds cached targeting', function () { @@ -373,8 +373,8 @@ describe('jwplayerRtdProvider', function() { enrichAdUnits([adUnit]); const bid1 = bids[0]; const bid2 = bids[1]; - expect(bid1).to.have.deep.property('jwTargeting', expectedTargetingForSuccess); - expect(bid2).to.have.deep.property('jwTargeting', expectedTargetingForSuccess); + expect(bid1.rtd.jwplayer).to.have.deep.property('targeting', expectedTargetingForSuccess); + expect(bid2.rtd.jwplayer).to.have.deep.property('targeting', expectedTargetingForSuccess); }); it('adds content block when segments are absent and no request is pending', function () { @@ -407,8 +407,8 @@ describe('jwplayerRtdProvider', function() { enrichAdUnits([adUnit]); const bid1 = bids[0]; const bid2 = bids[1]; - expect(bid1).to.have.deep.property('jwTargeting', expectedTargetingForFailure); - expect(bid2).to.have.deep.property('jwTargeting', expectedTargetingForFailure); + expect(bid1.rtd.jwplayer).to.have.deep.property('targeting', expectedTargetingForFailure); + expect(bid2.rtd.jwplayer).to.have.deep.property('targeting', expectedTargetingForFailure); }); }); @@ -456,6 +456,87 @@ describe('jwplayerRtdProvider', function() { }) }); + describe('Add Targeting to Bid', function () { + const targeting = {foo: 'bar'}; + + it('creates realTimeData when absent from Bid', function () { + const targeting = {foo: 'bar'}; + const bid = {}; + addTargetingToBid(bid, targeting); + expect(bid).to.have.property('rtd'); + expect(bid).to.have.nested.property('rtd.jwplayer.targeting', targeting); + }); + + it('adds to existing realTimeData', function () { + const otherRtd = { + targeting: { + seg: 'rtd seg' + } + }; + + const bid = { + rtd: { + otherRtd + } + }; + + addTargetingToBid(bid, targeting); + expect(bid).to.have.property('rtd'); + const rtd = bid.rtd; + expect(rtd).to.have.property('jwplayer'); + expect(rtd).to.have.nested.property('jwplayer.targeting', targeting); + + expect(rtd).to.have.deep.property('otherRtd', otherRtd); + }); + + it('adds to existing realTimeData.jwplayer', function () { + const otherInfo = { seg: 'rtd seg' }; + const bid = { + rtd: { + jwplayer: { + otherInfo + } + } + }; + addTargetingToBid(bid, targeting); + + expect(bid).to.have.property('rtd'); + const rtd = bid.rtd; + expect(rtd).to.have.property('jwplayer'); + expect(rtd).to.have.nested.property('jwplayer.otherInfo', otherInfo); + expect(rtd).to.have.nested.property('jwplayer.targeting', targeting); + }); + + it('overrides existing jwplayer.targeting', function () { + const otherInfo = { seg: 'rtd seg' }; + const bid = { + rtd: { + jwplayer: { + targeting: { + otherInfo + } + } + } + }; + addTargetingToBid(bid, targeting); + + expect(bid).to.have.property('rtd'); + const rtd = bid.rtd; + expect(rtd).to.have.property('jwplayer'); + expect(rtd).to.have.nested.property('jwplayer.targeting', targeting); + }); + + it('creates jwplayer when absent from realTimeData', function () { + const bid = { rtd: {} }; + addTargetingToBid(bid, targeting); + + expect(bid).to.have.property('rtd'); + const rtd = bid.rtd; + expect(rtd).to.have.property('jwplayer'); + expect(rtd).to.have.nested.property('jwplayer.targeting', targeting); + }); + }); + describe('jwplayerSubmodule', function () { it('successfully instantiates', function () { expect(jwplayerSubmodule.init()).to.equal(true); @@ -578,7 +659,7 @@ describe('jwplayerRtdProvider', function() { }; jwplayerSubmodule.getBidRequestData({ adUnits: [adUnitWithMediaId, adUnitEmpty] }, bidRequestSpy); expect(bidRequestSpy.calledOnce).to.be.true; - expect(bid).to.have.deep.property('jwTargeting', expectedTargeting); + expect(bid.rtd.jwplayer).to.have.deep.property('targeting', expectedTargeting); }); it('excludes segments when absent', function () { @@ -605,9 +686,9 @@ describe('jwplayerRtdProvider', function() { jwplayerSubmodule.getBidRequestData({ adUnits: [ adUnit ] }, bidRequestSpy); expect(bidRequestSpy.calledOnce).to.be.true; - expect(bid.jwTargeting).to.not.have.property('segments'); - expect(bid.jwTargeting).to.not.have.property('segments'); - expect(bid).to.have.deep.property('jwTargeting', expectedTargeting); + expect(bid.rtd.jwplayer.targeting).to.not.have.property('segments'); + expect(bid.rtd.jwplayer.targeting).to.not.have.property('segments'); + expect(bid.rtd.jwplayer).to.have.deep.property('targeting', expectedTargeting); }); it('does not modify bid when jwTargeting block is absent', function () { @@ -637,9 +718,9 @@ describe('jwplayerRtdProvider', function() { jwplayerSubmodule.getBidRequestData({ adUnits: [adUnitWithMediaId, adUnitEmpty, adUnitEmptyfpd] }, bidRequestSpy); expect(bidRequestSpy.calledOnce).to.be.true; - expect(bid1).to.not.have.property('jwTargeting'); - expect(bid2).to.not.have.property('jwTargeting'); - expect(bid3).to.not.have.property('jwTargeting'); + expect(bid1).to.not.have.property('rtd'); + expect(bid2).to.not.have.property('rtd'); + expect(bid3).to.not.have.property('rtd'); }); }); }); From 5f36be90bb816e84a68cff58ebc6999967dc3739 Mon Sep 17 00:00:00 2001 From: jdwieland8282 Date: Fri, 13 Nov 2020 12:24:57 -0700 Subject: [PATCH 007/103] Update pubCommonIdSystem.js adding Prebid GVLID to getStorageManager function --- modules/pubCommonIdSystem.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/pubCommonIdSystem.js b/modules/pubCommonIdSystem.js index 919e735d34e..acdfaefd5d0 100644 --- a/modules/pubCommonIdSystem.js +++ b/modules/pubCommonIdSystem.js @@ -20,8 +20,9 @@ const SHAREDID_URL = 'https://id.sharedid.org/id'; const SHAREDID_SUFFIX = '_sharedid'; const EXPIRED_COOKIE_DATE = 'Thu, 01 Jan 1970 00:00:01 GMT'; const SHAREDID_DEFAULT_STATE = false; +const GLVID = 887; -const storage = getStorageManager(null, 'pubCommonId'); +const storage = getStorageManager(GLVID, 'pubCommonId'); /** * Store sharedid in either cookie or local storage From 92a9a09b5509f266c7f69761b7c652f9586cc61f Mon Sep 17 00:00:00 2001 From: jdwieland8282 Date: Sun, 15 Nov 2020 06:01:35 -0700 Subject: [PATCH 008/103] Update pubCommonIdSystem.js (#5974) * Update pubCommonIdSystem.js adding GVLID to pubCommonIdSubmodule definition * Update pubCommonIdSystem.js adding GVLID to pubCommonIdSubmodule definition * Update pubCommonIdSystem.js removing duplicate reference to GVLID in pubCommonIdSubmodule defintion * Update pubCommonIdSystem.js fixing typo on ln 23 GLVID --> GVLID * Update pubCommonIdSystem.js fixing typos, * Update pubCommonIdSystem.js removing trailing white space * Update pubCommonIdSystem.js removing trailing white space --- modules/pubCommonIdSystem.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/modules/pubCommonIdSystem.js b/modules/pubCommonIdSystem.js index acdfaefd5d0..cb0c07cefa8 100644 --- a/modules/pubCommonIdSystem.js +++ b/modules/pubCommonIdSystem.js @@ -20,9 +20,9 @@ const SHAREDID_URL = 'https://id.sharedid.org/id'; const SHAREDID_SUFFIX = '_sharedid'; const EXPIRED_COOKIE_DATE = 'Thu, 01 Jan 1970 00:00:01 GMT'; const SHAREDID_DEFAULT_STATE = false; -const GLVID = 887; +const GVLID = 887; -const storage = getStorageManager(GLVID, 'pubCommonId'); +const storage = getStorageManager(GVLID, 'pubCommonId'); /** * Store sharedid in either cookie or local storage @@ -160,7 +160,11 @@ export const pubCommonIdSubmodule = { * @type {string} */ name: MODULE_NAME, - + /** + * Vendor id of prebid + * @type {Number} + */ + gvlid: GVLID, /** * Return a callback function that calls the pixelUrl with id as a query parameter * @param pixelUrl From 840c04d07e3bb7b2b569d4479c10751f7d1e1773 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Alberto=20Polo=20Garc=C3=ADa?= Date: Mon, 16 Nov 2020 09:22:01 +0100 Subject: [PATCH 009/103] [CriteoId] Add local storage check and migrate cookie check to use StorageManager API (#5953) * [CriteoId] Check if local storage is writable. * [CriteoId] Check cookies are writable using StorageManager Co-authored-by: Jesus Alberto Polo Garcia --- modules/criteoIdSystem.js | 17 ++++++----------- test/spec/modules/criteoIdSystem_spec.js | 3 +-- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/modules/criteoIdSystem.js b/modules/criteoIdSystem.js index 83bc773cb30..ac26d34d529 100644 --- a/modules/criteoIdSystem.js +++ b/modules/criteoIdSystem.js @@ -17,19 +17,11 @@ export const storage = getStorageManager(gvlid, bidderCode); const bididStorageKey = 'cto_bidid'; const bundleStorageKey = 'cto_bundle'; -const cookieWriteableKey = 'cto_test_cookie'; const cookiesMaxAge = 13 * 30 * 24 * 60 * 60 * 1000; const pastDateString = new Date(0).toString(); const expirationString = new Date(utils.timestamp() + cookiesMaxAge).toString(); -function areCookiesWriteable() { - storage.setCookie(cookieWriteableKey, '1'); - const canWrite = storage.getCookie(cookieWriteableKey) === '1'; - storage.setCookie(cookieWriteableKey, '', pastDateString); - return canWrite; -} - function extractProtocolHost (url, returnOnlyHost = false) { const parsedUrl = utils.parseUrl(url, {noDecodeWholeURL: true}) return returnOnlyHost @@ -60,20 +52,22 @@ function getCriteoDataFromAllStorages() { } } -function buildCriteoUsersyncUrl(topUrl, domain, bundle, areCookiesWriteable, isPublishertagPresent, gdprString) { +function buildCriteoUsersyncUrl(topUrl, domain, bundle, areCookiesWriteable, isLocalStorageWritable, isPublishertagPresent, gdprString) { const url = 'https://gum.criteo.com/sid/json?origin=prebid' + `${topUrl ? '&topUrl=' + encodeURIComponent(topUrl) : ''}` + `${domain ? '&domain=' + encodeURIComponent(domain) : ''}` + `${bundle ? '&bundle=' + encodeURIComponent(bundle) : ''}` + `${gdprString ? '&gdprString=' + encodeURIComponent(gdprString) : ''}` + `${areCookiesWriteable ? '&cw=1' : ''}` + - `${isPublishertagPresent ? '&pbt=1' : ''}` + `${isPublishertagPresent ? '&pbt=1' : ''}` + + `${isLocalStorageWritable ? '&lsw=1' : ''}`; return url; } function callCriteoUserSync(parsedCriteoData, gdprString) { - const cw = areCookiesWriteable(); + const cw = storage.cookiesAreEnabled(); + const lsw = storage.localStorageIsEnabled(); const topUrl = extractProtocolHost(getRefererInfo().referer); const domain = extractProtocolHost(document.location.href, true); const isPublishertagPresent = typeof criteo_pubtag !== 'undefined'; // eslint-disable-line camelcase @@ -83,6 +77,7 @@ function callCriteoUserSync(parsedCriteoData, gdprString) { domain, parsedCriteoData.bundle, cw, + lsw, isPublishertagPresent, gdprString ); diff --git a/test/spec/modules/criteoIdSystem_spec.js b/test/spec/modules/criteoIdSystem_spec.js index aa5807da0da..65e5aaf741d 100644 --- a/test/spec/modules/criteoIdSystem_spec.js +++ b/test/spec/modules/criteoIdSystem_spec.js @@ -71,7 +71,6 @@ describe('CriteoId module', function () { }); it('should call user sync url with the right params', function () { - getCookieStub.withArgs('cto_test_cookie').returns('1'); getCookieStub.withArgs('cto_bundle').returns('bundle'); window.criteo_pubtag = {} @@ -80,7 +79,7 @@ describe('CriteoId module', function () { ajaxBuilderStub.callsFake(mockResponse(undefined, ajaxStub)) criteoIdSubmodule.getId(); - const expectedUrl = `https://gum.criteo.com/sid/json?origin=prebid&topUrl=https%3A%2F%2Ftestdev.com%2F&domain=testdev.com&bundle=bundle&cw=1&pbt=1`; + const expectedUrl = `https://gum.criteo.com/sid/json?origin=prebid&topUrl=https%3A%2F%2Ftestdev.com%2F&domain=testdev.com&bundle=bundle&cw=1&pbt=1&lsw=1`; expect(ajaxStub.calledWith(expectedUrl)).to.be.true; From fb32955e72a99f1aea0b9c26b50f54da40dd5751 Mon Sep 17 00:00:00 2001 From: Anthony Lauzon Date: Mon, 16 Nov 2020 04:01:54 -0500 Subject: [PATCH 010/103] Audigent RTD configurable per-bidder segment mappings (#5903) * configurable per bidder segment mappings, optional segment caching, documentation update * rubicon->appnexus in docs * apply fpd convention, remove publisher defined mappers, add unit tests, fixes * segment mapping arr->dict * add fpd data to bids * update audigent tests * openrtb fpd data descriptions, update docs, more tests * update docs, update integration example * integration update * documentation table update * add examples of adding and overriding segment mappers * fix documentation * add contact verbiage * requestParams object type * brand rtd provider * update appnexus segment function * support numerical segments for appnexus * no default appnexus segments * update url * update gpt example * conditionally set bid segments for generic * fixes for integration example * update integration example * add appnexus handler override --- .../gpt/audigentSegments_example.html | 301 ------------------ .../gpt/haloRtdProvider_example.html | 149 +++++++++ modules/.submodules.json | 2 +- modules/audigentRtdProvider.js | 145 --------- modules/audigentRtdProvider.md | 76 ----- modules/haloRtdProvider.js | 198 ++++++++++++ modules/haloRtdProvider.md | 132 ++++++++ test/spec/modules/haloRtdProvider_spec.js | 220 +++++++++++++ 8 files changed, 700 insertions(+), 523 deletions(-) delete mode 100644 integrationExamples/gpt/audigentSegments_example.html create mode 100644 integrationExamples/gpt/haloRtdProvider_example.html delete mode 100644 modules/audigentRtdProvider.js delete mode 100644 modules/audigentRtdProvider.md create mode 100644 modules/haloRtdProvider.js create mode 100644 modules/haloRtdProvider.md create mode 100644 test/spec/modules/haloRtdProvider_spec.js diff --git a/integrationExamples/gpt/audigentSegments_example.html b/integrationExamples/gpt/audigentSegments_example.html deleted file mode 100644 index 1536ece9ab7..00000000000 --- a/integrationExamples/gpt/audigentSegments_example.html +++ /dev/null @@ -1,301 +0,0 @@ - - - - - - - - - - - - - - - -

Audigent Segments Prebid

- -
- -
- -TDID: -
-
- -Audigent Segments: -
-
- - diff --git a/integrationExamples/gpt/haloRtdProvider_example.html b/integrationExamples/gpt/haloRtdProvider_example.html new file mode 100644 index 00000000000..7f9a34e55ee --- /dev/null +++ b/integrationExamples/gpt/haloRtdProvider_example.html @@ -0,0 +1,149 @@ + + + + + + + + + + + + + + + +

Audigent Segments Prebid

+ +
+ +
+ +Halo Id: +
+
+ +Audigent Segments (Appnexus): +
+
+ + diff --git a/modules/.submodules.json b/modules/.submodules.json index 9b523a0c73a..53caf7a0671 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -26,7 +26,7 @@ ], "rtdModule": [ "browsiRtdProvider", - "audigentRtdProvider", + "haloRtdProvider", "jwplayerRtdProvider", "reconciliationRtdProvider" ] diff --git a/modules/audigentRtdProvider.js b/modules/audigentRtdProvider.js deleted file mode 100644 index 09b76cac0df..00000000000 --- a/modules/audigentRtdProvider.js +++ /dev/null @@ -1,145 +0,0 @@ -/** - * This module adds audigent provider to the real time data module - * The {@link module:modules/realTimeData} module is required - * The module will fetch segments from audigent server - * @module modules/audigentRtdProvider - * @requires module:modules/realTimeData - */ -import {getGlobal} from '../src/prebidGlobal.js'; -import * as utils from '../src/utils.js'; -import {submodule} from '../src/hook.js'; -import {ajax} from '../src/ajax.js'; -import { getStorageManager } from '../src/storageManager.js'; - -const storage = getStorageManager(); - -/** @type {string} */ -const MODULE_NAME = 'realTimeData'; -const SUBMODULE_NAME = 'audigent'; -const HALOID_LOCAL_NAME = 'auHaloId'; -const SEG_LOCAL_NAME = '__adgntseg'; - -/** - * decorate adUnits with segment data - * @param {adUnit[]} adUnits - * @param {Object} data - */ -function addSegmentData(adUnits, data) { - adUnits.forEach(adUnit => { - if (adUnit.hasOwnProperty('bids')) { - adUnit.bids.forEach(bid => { - bid.audigent_segments = data; - }) - } - }) - - return adUnits; -} - -/** - * segment retrieval from audigent's backends - * @param {Object} reqBidsConfigObj - * @param {function} onDone - * @param {Object} config - * @param {Object} userConsent - */ -function getSegments(reqBidsConfigObj, onDone, config, userConsent) { - const adUnits = reqBidsConfigObj.adUnits || getGlobal().adUnits; - - let jsonData = storage.getDataFromLocalStorage(SEG_LOCAL_NAME); - if (jsonData) { - let data = JSON.parse(jsonData); - if (data.audigent_segments) { - addSegmentData(adUnits, data.audigent_segments); - onDone(); - return; - } - } - - const userIds = (getGlobal()).getUserIds(); - if (typeof userIds == 'undefined' || userIds == null) { - onDone(); - return; - } - - let haloId = storage.getDataFromLocalStorage(HALOID_LOCAL_NAME); - if (haloId) { - userIds.haloId = haloId; - getSegmentsAsync(adUnits, onDone, config, userConsent, userIds); - } else { - var script = document.createElement('script') - script.type = 'text/javascript'; - - script.onload = function() { - userIds.haloId = storage.getDataFromLocalStorage(HALOID_LOCAL_NAME); - getSegmentsAsync(adUnits, onDone, config, userConsent, userIds); - } - - script.src = 'https://id.halo.ad.gt/api/v1/haloid'; - document.getElementsByTagName('head')[0].appendChild(script); - } -} - -/** - * async segment retrieval from audigent's backends - * @param {adUnit[]} adUnits - * @param {function} onDone - * @param {Object} config - * @param {Object} userConsent - * @param {Object} userIds - */ -function getSegmentsAsync(adUnits, onDone, config, userConsent, userIds) { - let reqParams = {} - if (typeof config == 'object' && config != null && Object.keys(config).length > 0) { - reqParams = config.params - } - - const url = `https://seg.halo.ad.gt/api/v1/rtb_segments`; - ajax(url, { - success: function (response, req) { - if (req.status === 200) { - try { - const data = JSON.parse(response); - if (data && data.audigent_segments) { - addSegmentData(adUnits, data.audigent_segments); - onDone(); - storage.setDataInLocalStorage(SEG_LOCAL_NAME, JSON.stringify(data)); - } else { - onDone(); - } - } catch (err) { - utils.logError('unable to parse audigent segment data'); - onDone(); - } - } else if (req.status === 204) { - // unrecognized partner config - onDone(); - } - }, - error: function () { - onDone(); - utils.logError('unable to get audigent segment data'); - } - }, - JSON.stringify({'userIds': userIds, 'config': reqParams}), - {contentType: 'application/json'} - ); -} - -/** - * module init - * @param {Object} config - * @return {boolean} - */ -export function init(config) { - return true; -} - -/** @type {RtdSubmodule} */ -export const audigentSubmodule = { - name: SUBMODULE_NAME, - getBidRequestData: getSegments, - init: init -}; - -submodule(MODULE_NAME, audigentSubmodule); diff --git a/modules/audigentRtdProvider.md b/modules/audigentRtdProvider.md deleted file mode 100644 index 03e647f651d..00000000000 --- a/modules/audigentRtdProvider.md +++ /dev/null @@ -1,76 +0,0 @@ -## Audigent Real-time Data Submodule - -Audigent is a next-generation data management platform and a first-of-a-kind -"data agency" containing some of the most exclusive content-consuming audiences -across desktop, mobile and social platforms. - -This real-time data module provides quality user segmentation that can be -attached to bid request objects destined for different SSPs in order to optimize -targeting. Audigent maintains a large database of first-party Tradedesk Unified -ID, Audigent Halo ID and other id provider mappings to various third-party -segment types that are utilizable across different SSPs. With this module, -these segments can be retrieved and supplied to the SSP in real-time during -the bid request cycle. - -### Usage - -Compile the audigent RTD module into your Prebid build: - -`gulp build --modules=userId,unifiedIdSystem,rtdModule,audigentRtdProvider,rubiconBidAdapter` - -Configure Prebid to add the Audigent RTD Segment Handler: -``` -pbjs.setConfig( - ... - realTimeData: { - auctionDelay: 1000, - dataProviders: [ - { - name: "audigent", - waitForIt: true - } - ] - } - ... -} -``` - -Audigent segments will then be attached to each bid request objects in -`bid.realTimeData.audigent_segments` - -The format of the segments is a per-SSP mapping: - -``` -{ - 'appnexus': ['anseg1', 'anseg2'], - 'google': ['gseg1', 'gseg2'] -} -``` - -If a given SSP's API backend supports segment fields, they can then be -attached prior to the bid request being sent: - -``` -pbjs.requestBids({bidsBackHandler: addAudigentSegments}); - -function addAudigentSegments() { - for (i = 0; i < adUnits.length; i++) { - let adUnit = adUnits[i]; - for (j = 0; j < adUnit.bids.length; j++) { - adUnit.bids[j].userId.lipb.segments = adUnit.bids[j].audigent_segments['rubicon']; - } - } -} -``` - -### Testing - -To view an example of available segments returned by Audigent's backends: - -`gulp serve --modules=userId,unifiedIdSystem,rtdModule,audigentRtdProvider,rubiconBidAdapter` - -and then point your browser at: - -`http://localhost:9999/integrationExamples/gpt/audigentSegments_example.html` - - diff --git a/modules/haloRtdProvider.js b/modules/haloRtdProvider.js new file mode 100644 index 00000000000..fff9e43ea2a --- /dev/null +++ b/modules/haloRtdProvider.js @@ -0,0 +1,198 @@ +/** + * This module adds audigent provider to the real time data module + * The {@link module:modules/realTimeData} module is required + * The module will fetch segments from audigent server + * @module modules/audigentRtdProvider + * @requires module:modules/realTimeData + */ +import {getGlobal} from '../src/prebidGlobal.js'; +import * as utils from '../src/utils.js'; +import {submodule} from '../src/hook.js'; +import {ajax} from '../src/ajax.js'; +import { getStorageManager } from '../src/storageManager.js'; + +export const storage = getStorageManager(); + +/** @type {string} */ +const MODULE_NAME = 'realTimeData'; +const SUBMODULE_NAME = 'halo'; + +export const HALOID_LOCAL_NAME = 'auHaloId'; +export const SEG_LOCAL_NAME = '__adgntseg'; + +const set = (obj, path, val) => { + const keys = path.split('.'); + const lastKey = keys.pop(); + const lastObj = keys.reduce((obj, key) => obj[key] = obj[key] || {}, obj); + lastObj[lastKey] = lastObj[lastKey] || val; +}; + +/** bid adapter format segment augmentation functions */ +const segmentMappers = { + appnexus: function(bid, segments) { + set(bid, 'params.user.segments', []); + let appnexusSegments = []; + segments.forEach(segment => { + if (typeof segment.value != 'undefined' && segment.value != null) { + let appnexusSegment = {'id': segment.id, 'value': segment.value}; + appnexusSegments.push(appnexusSegment); + } + }) + bid.params.user.segments = bid.params.user.segments.concat(appnexusSegments); + }, + generic: function(bid, segments) { + bid.segments = bid.segments || []; + if (Array.isArray(bid.segments)) { + bid.segments = bid.segments.concat(segments); + } + } +} + +/** + * decorate adUnits with segment data + * @param {adUnit[]} adUnits + * @param {Object} data + */ +export function addSegmentData(adUnits, segmentData, config) { + adUnits.forEach(adUnit => { + if (adUnit.hasOwnProperty('bids')) { + adUnit.bids.forEach(bid => { + try { + set(bid, 'fpd.user.data', []); + if (Array.isArray(bid.fpd.user.data)) { + bid.fpd.user.data.forEach(fpdData => { + let segments = segmentData[fpdData.id] || segmentData[fpdData.name] || []; + fpdData.segment = (fpdData.segment || []).concat(segments); + }); + } + } catch (err) { + utils.logError(err.message); + } + + try { + if (config.params.mapSegments && config.params.mapSegments[bid.bidder] && segmentData[bid.bidder]) { + if (typeof config.params.mapSegments[bid.bidder] == 'function') { + config.params.mapSegments[bid.bidder](bid, segmentData[bid.bidder]); + } else if (segmentMappers[bid.bidder]) { + segmentMappers[bid.bidder](bid, segmentData[bid.bidder]); + } + } + } catch (err) { + utils.logError(err.message); + } + }); + } + }); + + return adUnits; +} + +/** + * segment retrieval from audigent's backends + * @param {Object} reqBidsConfigObj + * @param {function} onDone + * @param {Object} config + * @param {Object} userConsent + */ +export function getSegments(reqBidsConfigObj, onDone, config, userConsent) { + const adUnits = reqBidsConfigObj.adUnits || getGlobal().adUnits; + + if (config.params.segmentCache) { + let jsonData = storage.getDataFromLocalStorage(SEG_LOCAL_NAME); + + if (jsonData) { + let data = JSON.parse(jsonData); + + if (data.audigent_segments) { + addSegmentData(adUnits, data.audigent_segments, config); + onDone(); + return; + } + } + } + + const userIds = (getGlobal()).getUserIds(); + + let haloId = storage.getDataFromLocalStorage(HALOID_LOCAL_NAME); + if (haloId) { + userIds.haloId = haloId; + getSegmentsAsync(adUnits, onDone, config, userConsent, userIds); + } else { + var script = document.createElement('script') + script.type = 'text/javascript'; + + script.onload = function() { + userIds.haloId = storage.getDataFromLocalStorage(HALOID_LOCAL_NAME); + getSegmentsAsync(adUnits, onDone, config, userConsent, userIds); + } + + script.src = 'https://id.halo.ad.gt/api/v1/haloid'; + document.getElementsByTagName('head')[0].appendChild(script); + } +} + +/** + * async segment retrieval from audigent's backends + * @param {adUnit[]} adUnits + * @param {function} onDone + * @param {Object} config + * @param {Object} userConsent + * @param {Object} userIds + */ +export function getSegmentsAsync(adUnits, onDone, config, userConsent, userIds) { + let reqParams = {}; + if (typeof config == 'object' && config != null) { + set(config, 'params.requestParams', {}); + reqParams = config.params.requestParams; + } + + const url = `https://seg.halo.ad.gt/api/v1/rtb_segments`; + ajax(url, { + success: function (response, req) { + if (req.status === 200) { + try { + const data = JSON.parse(response); + if (data && data.audigent_segments) { + addSegmentData(adUnits, data.audigent_segments, config); + onDone(); + storage.setDataInLocalStorage(SEG_LOCAL_NAME, JSON.stringify(data)); + } else { + onDone(); + } + } catch (err) { + utils.logError('unable to parse audigent segment data'); + onDone(); + } + } else if (req.status === 204) { + // unrecognized partner config + onDone(); + } + }, + error: function () { + onDone(); + utils.logError('unable to get audigent segment data'); + } + }, + JSON.stringify({'userIds': userIds, 'config': reqParams}), + {contentType: 'application/json'} + ); +} + +/** + * module init + * @param {Object} provider + * @param {Objkect} userConsent + * @return {boolean} + */ +function init(provider, userConsent) { + return true; +} + +/** @type {RtdSubmodule} */ +export const haloSubmodule = { + name: SUBMODULE_NAME, + getBidRequestData: getSegments, + init: init +}; + +submodule(MODULE_NAME, haloSubmodule); diff --git a/modules/haloRtdProvider.md b/modules/haloRtdProvider.md new file mode 100644 index 00000000000..2897a5917fa --- /dev/null +++ b/modules/haloRtdProvider.md @@ -0,0 +1,132 @@ +## Audigent Halo Real-time Data Submodule + +Audigent is a next-generation data management platform and a first-of-a-kind +"data agency" containing some of the most exclusive content-consuming audiences +across desktop, mobile and social platforms. + +This real-time data module provides quality user segmentation that can be +attached to bid request objects destined for different SSPs in order to optimize +targeting. Audigent maintains a large database of first-party Tradedesk Unified +ID, Audigent Halo ID and other id provider mappings to various third-party +segment types that are utilizable across different SSPs. With this module, +these segments can be retrieved and supplied to the SSP in real-time during +the bid request cycle. + +### Publisher Usage + +Compile the Halo RTD module into your Prebid build: + +`gulp build --modules=userId,unifiedIdSystem,rtdModule,audigentRtdProvider,appnexusBidAdapter` + +Add the Halo RTD provider to your Prebid config. For any adapters +that you would like to retrieve segments for, add a mapping in the 'mapSegments' +parameter. In this example we will configure publisher 1234 to retrieve +appnexus segments from Audigent. See the "Parameter Descriptions" below for +more detailed information of the configuration parameters. Currently, +OpenRTB compatible fpd data will be added for any bid adapter in the +"mapSegments" objects. Automated bid augmentation exists for some bidders. +Please work with your Audigent Prebid support team (prebid@audigent.com) on +which version of Prebid.js supports which bidders automatically. + +``` +pbjs.setConfig( + ... + realTimeData: { + auctionDelay: auctionDelay, + dataProviders: [ + { + name: "halo", + waitForIt: true, + params: { + mapSegments: { + appnexus: true, + }, + segmentCache: false, + requestParams: { + publisherId: 1234 + } + } + } + ] + } + ... +} +``` + +### Parameter Descriptions for the Halo `dataProviders` Configuration Section + +| Name |Type | Description | Notes | +| :------------ | :------------ | :------------ |:------------ | +| name | String | Real time data module name | Always 'halo' | +| waitForIt | Boolean | Required to ensure that the auction is delayed until prefetch is complete | Optional. Defaults to false | +| params | Object | | | +| params.mapSegments | Boolean | Dictionary of bidders you would like to supply Audigent segments for. Maps to boolean values, but also allows functions for custom mapping logic. The function signature is (bid, segments) => {}. | Required | +| params.segmentCache | Boolean | This parameter tells the Halo RTD module to attempt reading segments from a local storage cache instead of always requesting them from the Audigent server. | Optional. Defaults to false. | +| params.requestParams | Object | Publisher partner specific configuration options, such as optional publisher id and other segment query related metadata to be submitted to Audigent's backend with each request. Contact prebid@audigent.com for more information. | Optional | + +### Overriding & Adding Segment Mappers +As indicated above, it is possible to provide your own bid augmentation +functions. This is useful if you know a bid adapter's API supports segment +fields which aren't specifically being added to request objects in the Prebid +bid adapter. You can also override segment mappers by passing a function +instead of a boolean to the Halo RTD segment module. This might be useful +if you'd like to use custom logic to determine which segments are sent +to a specific backend. + +Please see the following example, which provides a function to modify bids for +a bid adapter called adBuzz and overrides the appnexus segment mapper. + +``` +pbjs.setConfig( + ... + realTimeData: { + auctionDelay: auctionDelay, + dataProviders: [ + { + name: "halo", + waitForIt: true, + params: { + mapSegments: { + // adding an adBuzz segment mapper + adBuzz: function(bid, segments) { + bid.params.adBuzzCustomSegments = []; + for (var i = 0; i < segments.length; i++) { + bid.params.adBuzzCustomSegments.push(segments[i].id); + } + }, + // overriding the appnexus segment mapper to exclude certain segments + appnexus: function(bid, segments) { + for (var i = 0; i < segments.length; i++) { + if (segments[i].id != 'exclude_segment') { + bid.params.user.segments.push(segments[i].id); + } + } + } + }, + segmentCache: false, + requestParams: { + publisherId: 1234 + } + } + } + ] + } + ... +} +``` + +More examples can be viewed in the haloRtdAdapter_spec.js tests. + +### Testing + +To view an example of available segments returned by Audigent's backends: + +`gulp serve --modules=userId,unifiedIdSystem,rtdModule,haloRtdProvider,appnexusBidAdapter` + +and then point your browser at: + +`http://localhost:9999/integrationExamples/gpt/haloRtdProvider_example.html` + + + + diff --git a/test/spec/modules/haloRtdProvider_spec.js b/test/spec/modules/haloRtdProvider_spec.js new file mode 100644 index 00000000000..17c2e19e1a6 --- /dev/null +++ b/test/spec/modules/haloRtdProvider_spec.js @@ -0,0 +1,220 @@ +import { HALOID_LOCAL_NAME, SEG_LOCAL_NAME, addSegmentData, getSegments, haloSubmodule, storage } from 'modules/haloRtdProvider.js'; +import { server } from 'test/mocks/xhr.js'; + +const responseHeader = {'Content-Type': 'application/json'}; + +describe('haloRtdProvider', function() { + describe('haloSubmodule', function() { + it('successfully instantiates', function () { + expect(haloSubmodule.init()).to.equal(true); + }); + }); + + describe('Add Segment Data', function() { + it('adds segment data', function() { + const config = { + params: { + mapSegments: { + 'appnexus': true, + 'generic': true + } + } + }; + + let adUnits = [ + { + bids: [ + // bid with existing segment data in bid obj and fpd + { + bidder: 'appnexus', + fpd: { + user: { + data: [ + { + id: 'appnexus', + segment: [ + { + id: 'apnseg0' + } + ] + } + ] + } + }, + params: { + user: { + segments: [{'id': 'apnseg0', 'value': 0}] + } + } + } + ] + }, + + // bids with fpd data definitions but without existing segment data + { + bids: [ + { + bidder: 'appnexus', + fpd: { + user: { + data: [ + { + id: 'appnexus' + } + ] + } + } + }, + { + bidder: 'generic', + fpd: { + user: { + data: [ + { + id: 'generic' + } + ] + } + } + } + ] + } + ]; + + const data = { + appnexus: [{id: 'apnseg1', value: 0}, {id: 'apnseg2', value: 2}, {id: 'apnseg3'}], + generic: [{id: 'seg1'}, {id: 'seg2'}, {id: 'seg3'}] + }; + + addSegmentData(adUnits, data, config); + + expect(adUnits[0].bids[0].fpd.user.data[0].segment[0]).to.have.deep.property('id', 'apnseg0'); + expect(adUnits[0].bids[0].fpd.user.data[0].segment[1]).to.have.deep.property('id', 'apnseg1'); + expect(adUnits[0].bids[0].fpd.user.data[0].segment[2]).to.have.deep.property('id', 'apnseg2'); + expect(adUnits[0].bids[0].fpd.user.data[0].segment[3]).to.have.deep.property('id', 'apnseg3'); + expect(adUnits[0].bids[0].params.user).to.have.deep.property('segments', [{'id': 'apnseg0', 'value': 0}, {'id': 'apnseg1', 'value': 0}, {'id': 'apnseg2', 'value': 2}]); + + expect(adUnits[1].bids[0].fpd.user.data[0].segment[0]).to.have.deep.property('id', 'apnseg1'); + expect(adUnits[1].bids[0].fpd.user.data[0].segment[1]).to.have.deep.property('id', 'apnseg2'); + expect(adUnits[1].bids[0].fpd.user.data[0].segment[2]).to.have.deep.property('id', 'apnseg3'); + expect(adUnits[1].bids[0].params.user).to.have.deep.property('segments', [{'id': 'apnseg1', 'value': 0}, {'id': 'apnseg2', 'value': 2}]); + + expect(adUnits[1].bids[1].fpd.user.data[0].segment[0]).to.have.deep.property('id', 'seg1'); + expect(adUnits[1].bids[1].fpd.user.data[0].segment[1]).to.have.deep.property('id', 'seg2'); + expect(adUnits[1].bids[1].fpd.user.data[0].segment[2]).to.have.deep.property('id', 'seg3'); + expect(adUnits[1].bids[1].segments[0]).to.have.deep.property('id', 'seg1'); + expect(adUnits[1].bids[1].segments[1]).to.have.deep.property('id', 'seg2'); + expect(adUnits[1].bids[1].segments[2]).to.have.deep.property('id', 'seg3'); + }); + + it('allows mapper extensions and overrides', function() { + const config = { + params: { + mapSegments: { + generic: (bid, segments) => { + bid.overrideSegments = segments; + }, + newBidder: (bid, segments) => { + bid.newBidderSegments = segments; + } + } + } + }; + + let adUnits = [ + { + bids: [ {bidder: 'newBidder'}, {bidder: 'generic'} ] + } + ]; + + const data = { + newBidder: [{id: 'nbseg1', name: 'New Bidder Segment 1'}, {id: 'nbseg2', name: 'New Bidder Segment 2'}, {id: 'nbseg3', name: 'New Bidder Segment 3'}], + generic: [{id: 'seg1'}, {id: 'seg2'}, {id: 'seg3'}] + }; + + addSegmentData(adUnits, data, config); + + expect(adUnits[0].bids[0].newBidderSegments[0]).to.have.deep.property('id', 'nbseg1'); + expect(adUnits[0].bids[0].newBidderSegments[0]).to.have.deep.property('name', 'New Bidder Segment 1'); + expect(adUnits[0].bids[0].newBidderSegments[1]).to.have.deep.property('id', 'nbseg2'); + expect(adUnits[0].bids[0].newBidderSegments[1]).to.have.deep.property('name', 'New Bidder Segment 2'); + expect(adUnits[0].bids[0].newBidderSegments[2]).to.have.deep.property('id', 'nbseg3'); + expect(adUnits[0].bids[0].newBidderSegments[2]).to.have.deep.property('name', 'New Bidder Segment 3'); + + expect(adUnits[0].bids[1].overrideSegments[0]).to.have.deep.property('id', 'seg1'); + expect(adUnits[0].bids[1].overrideSegments[1]).to.have.deep.property('id', 'seg2'); + expect(adUnits[0].bids[1].overrideSegments[2]).to.have.deep.property('id', 'seg3'); + }); + }); + + describe('Get Segments', function() { + it('gets segment data from local storage cache', function() { + const config = { + params: { + segmentCache: true, + mapSegments: { + 'generic': true + } + } + }; + + let reqBidsConfigObj = { + adUnits: [ + { + bids: [{bidder: 'generic'}] + } + ] + }; + + const data = { + audigent_segments: { + generic: [{id: 'seg1'}] + } + }; + + storage.setDataInLocalStorage(SEG_LOCAL_NAME, JSON.stringify(data)); + + getSegments(reqBidsConfigObj, () => {}, config, {}); + + expect(reqBidsConfigObj.adUnits[0].bids[0].segments[0]).to.have.deep.property('id', 'seg1'); + }); + + it('gets segment data via async request', function() { + const config = { + params: { + segmentCache: false, + mapSegments: { + 'generic': true + }, + requestParams: { + 'publisherId': 1234 + } + } + }; + + let reqBidsConfigObj = { + adUnits: [ + { + bids: [{bidder: 'generic'}] + } + ] + }; + const data = { + audigent_segments: { + generic: [{id: 'seg1'}] + } + }; + + storage.setDataInLocalStorage(HALOID_LOCAL_NAME, 'haloid'); + getSegments(reqBidsConfigObj, () => {}, config, {}); + + let request = server.requests[0]; + let postData = JSON.parse(request.requestBody); + expect(postData.config).to.have.deep.property('publisherId', 1234); + + request.respond(200, responseHeader, JSON.stringify(data)); + + expect(reqBidsConfigObj.adUnits[0].bids[0].segments[0]).to.have.deep.property('id', 'seg1'); + }); + }); +}); From ac686e5c0e65cc9a8babf90cb8fabc9694064a94 Mon Sep 17 00:00:00 2001 From: r-schweitzer <50628828+r-schweitzer@users.noreply.github.com> Date: Mon, 16 Nov 2020 18:43:14 +0100 Subject: [PATCH 011/103] added feature/getNoBidsForAdUnitCode function (#5932) * added feature/getNoBidsForAdUnitCode function * added unit test --- src/prebid.js | 12 ++++++++++++ test/spec/api_spec.js | 6 +++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/prebid.js b/src/prebid.js index 8bfb6024d7a..0f72ca878e5 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -258,6 +258,18 @@ $$PREBID_GLOBAL$$.getNoBids = function () { return getBids('getNoBids'); }; +/** + * This function returns the bids requests involved in an auction but not bid on or the specified adUnitCode + * @param {string} adUnitCode adUnitCode + * @alias module:pbjs.getNoBidsForAdUnitCode + * @return {Object} bidResponse object + */ + +$$PREBID_GLOBAL$$.getNoBidsForAdUnitCode = function (adUnitCode) { + const bids = auctionManager.getNoBids().filter(bid => bid.adUnitCode === adUnitCode); + return { bids }; +}; + /** * This function returns the bid responses at the given moment. * @alias module:pbjs.getBidResponses diff --git a/test/spec/api_spec.js b/test/spec/api_spec.js index 6f21eba7aaf..6d67565056f 100755 --- a/test/spec/api_spec.js +++ b/test/spec/api_spec.js @@ -43,10 +43,14 @@ describe('Publisher API', function () { assert.isFunction($$PREBID_GLOBAL$$.getBidResponses); }); - it('should have function $$PREBID_GLOBAL$$.getBidResponses', function () { + it('should have function $$PREBID_GLOBAL$$.getNoBids', function () { assert.isFunction($$PREBID_GLOBAL$$.getNoBids); }); + it('should have function $$PREBID_GLOBAL$$.getNoBidsForAdUnitCode', function () { + assert.isFunction($$PREBID_GLOBAL$$.getNoBidsForAdUnitCode); + }); + it('should have function $$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode', function () { assert.isFunction($$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode); }); From a4a194c0086ed3ea3a13315798fc7f05abef1442 Mon Sep 17 00:00:00 2001 From: Mehmet Can Kurt Date: Mon, 16 Nov 2020 13:51:29 -0800 Subject: [PATCH 012/103] update quantcastBidAdapter to make user sync flag non-constant (#5949) --- modules/quantcastBidAdapter.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/modules/quantcastBidAdapter.js b/modules/quantcastBidAdapter.js index 894bb991a71..347ebc2ff55 100644 --- a/modules/quantcastBidAdapter.js +++ b/modules/quantcastBidAdapter.js @@ -101,6 +101,8 @@ function checkTCFv2(tcData) { return !!(vendorConsent && purposeConsent); } +let hasUserSynced = false; + /** * The documentation for Prebid.js Adapter 1.0 can be found at link below, * http://prebid.org/dev-docs/bidder-adapter-1.html @@ -109,7 +111,6 @@ export const spec = { code: BIDDER_CODE, GVLID: 11, supportedMediaTypes: ['banner', 'video'], - hasUserSynced: false, /** * Verify the `AdUnits.bids` response with `true` for valid request and `false` @@ -271,7 +272,7 @@ export const spec = { }, getUserSyncs(syncOptions, serverResponses) { const syncs = [] - if (!this.hasUserSynced && syncOptions.pixelEnabled) { + if (!hasUserSynced && syncOptions.pixelEnabled) { const responseWithUrl = find(serverResponses, serverResponse => utils.deepAccess(serverResponse.body, 'userSync.url') ); @@ -283,12 +284,12 @@ export const spec = { url: url }); } - this.hasUserSynced = true; + hasUserSynced = true; } return syncs; }, resetUserSync() { - this.hasUserSynced = false; + hasUserSynced = false; } }; From a126dcf06fc320f180e97a8ffef87c5b1d0744fb Mon Sep 17 00:00:00 2001 From: pro-nsk <32703851+pro-nsk@users.noreply.github.com> Date: Tue, 17 Nov 2020 04:52:26 +0700 Subject: [PATCH 013/103] Update for Qwarry bid adapter: onBidWon hotfix (#5955) * qwarry bid adapter * formatting fixes * fix tests for qwarry * qwarry bid adapter * add header for qwarry bid adapter * bid requests fix * fix tests * response fix * fix tests for Qwarry bid adapter * add pos parameter to qwarry bid adapter * qwarryBidAdapter onBidWon hotfix Co-authored-by: Artem Kostritsa Co-authored-by: Alexander Kascheev --- modules/qwarryBidAdapter.js | 4 +- test/spec/modules/qwarryBidAdapter_spec.js | 67 +++++++++++++--------- 2 files changed, 43 insertions(+), 28 deletions(-) diff --git a/modules/qwarryBidAdapter.js b/modules/qwarryBidAdapter.js index 0c6dee9be69..7c2ec0f085b 100644 --- a/modules/qwarryBidAdapter.js +++ b/modules/qwarryBidAdapter.js @@ -66,7 +66,9 @@ export const spec = { onBidWon: function (bid) { if (bid.winUrl) { - ajax(bid.winUrl, null); + const cpm = bid.cpm; + const winUrl = bid.winUrl.replace(/\$\{AUCTION_PRICE\}/, cpm); + ajax(winUrl, null); return true; } return false; diff --git a/test/spec/modules/qwarryBidAdapter_spec.js b/test/spec/modules/qwarryBidAdapter_spec.js index 02fe9c4538b..91e3cf4bfdf 100644 --- a/test/spec/modules/qwarryBidAdapter_spec.js +++ b/test/spec/modules/qwarryBidAdapter_spec.js @@ -11,33 +11,37 @@ const REQUEST = { } } -const BIDDER_BANNER_RESPONSE = {'prebidResponse': [{ - 'ad': '
test
', - 'requestId': 'e64782a4-8e68-4c38-965b-80ccf115d46d', - 'cpm': 900.5, - 'currency': 'USD', - 'width': 640, - 'height': 480, - 'ttl': 300, - 'creativeId': 1, - 'netRevenue': true, - 'winUrl': 'http://test.com', - 'format': 'banner' -}]} - -const BIDDER_VIDEO_RESPONSE = {'prebidResponse': [{ - 'ad': 'vast', - 'requestId': 'e64782a4-8e68-4c38-965b-80ccf115d46z', - 'cpm': 800.4, - 'currency': 'USD', - 'width': 1024, - 'height': 768, - 'ttl': 200, - 'creativeId': 2, - 'netRevenue': true, - 'winUrl': 'http://test.com', - 'format': 'video' -}]} +const BIDDER_BANNER_RESPONSE = { + 'prebidResponse': [{ + 'ad': '
test
', + 'requestId': 'e64782a4-8e68-4c38-965b-80ccf115d46d', + 'cpm': 900.5, + 'currency': 'USD', + 'width': 640, + 'height': 480, + 'ttl': 300, + 'creativeId': 1, + 'netRevenue': true, + 'winUrl': 'http://test.com', + 'format': 'banner' + }] +} + +const BIDDER_VIDEO_RESPONSE = { + 'prebidResponse': [{ + 'ad': 'vast', + 'requestId': 'e64782a4-8e68-4c38-965b-80ccf115d46z', + 'cpm': 800.4, + 'currency': 'USD', + 'width': 1024, + 'height': 768, + 'ttl': 200, + 'creativeId': 2, + 'netRevenue': true, + 'winUrl': 'http://test.com', + 'format': 'video' + }] +} const BIDDER_NO_BID_RESPONSE = '' @@ -120,4 +124,13 @@ describe('qwarryBidAdapter', function () { expect(result).to.deep.equal([]) }) }) + + describe('onBidWon', function () { + it('handles banner win: should get true', function () { + const win = BIDDER_BANNER_RESPONSE.prebidResponse[0] + const bidWonResult = spec.onBidWon(win) + + expect(bidWonResult).to.equal(true) + }) + }) }) From 1664f4e2648ea90a93b25c76510858c0de30fa69 Mon Sep 17 00:00:00 2001 From: jsut Date: Mon, 16 Nov 2020 16:53:02 -0500 Subject: [PATCH 014/103] FIX typo's in CONTRIBUTING (#5956) --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 962e057fbc5..606d26cd25a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,7 +3,7 @@ Contributions are always welcome. To contribute, [fork](https://help.github.com/ commit your changes, and [open a pull request](https://help.github.com/articles/using-pull-requests/) against the master branch. -Pull requests must have 80% code coverage before beign considered for merge. +Pull requests must have 80% code coverage before being considered for merge. Additional details about the process can be found [here](./PR_REVIEW.md). There are more details available if you'd like to contribute a [bid adapter](https://docs.prebid.org/dev-docs/bidder-adaptor.html) or [analytics adapter](https://docs.prebid.org/dev-docs/integrate-with-the-prebid-analytics-api.html). @@ -59,7 +59,7 @@ When you are adding code to Prebid.js, or modifying code that isn't covered by a Prebid.js already has many tests. Read them to see how Prebid.js is tested, and for inspiration: - Look in `test/spec` and its subdirectories -- Tests for bidder adaptors are located in `test/spec/modules` +- Tests for bidder adapters are located in `test/spec/modules` A test module might have the following general structure: From 5aa0fe65061578446c59d6659e799c5c3df5b3bc Mon Sep 17 00:00:00 2001 From: jsut Date: Mon, 16 Nov 2020 16:54:03 -0500 Subject: [PATCH 015/103] Add a note to the readme about adapter aliases (#5968) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 44882570d89..40df62ccee4 100644 --- a/README.md +++ b/README.md @@ -142,7 +142,7 @@ This runs some code quality checks, starts a web server at `http://localhost:999 ### Build Optimization -The standard build output contains all the available modules from within the `modules` folder. +The standard build output contains all the available modules from within the `modules` folder. Note, however that there are bid adapters which support multiple bidders through aliases, so if you don't see a file in modules for a bid adapter, you may need to grep the repository to find the name of the module you need to include. You might want to exclude some/most of them from the final bundle. To make sure the build only includes the modules you want, you can specify the modules to be included with the `--modules` CLI argument. From c04bd6d70c1107ebfd69f7c838e94485408e454f Mon Sep 17 00:00:00 2001 From: Mehmet Can Kurt Date: Mon, 16 Nov 2020 17:07:08 -0800 Subject: [PATCH 016/103] update quantcastBidAdapter to pass quantcast fpa in the bid request (#5947) * update quantcastBidAdapter to pass quantcast fpa in the bid request * remove empty line from lunamediahBidAdapter * pass quantcast vendor id when obtaining storage manager --- modules/quantcastBidAdapter.js | 14 ++++++++-- test/spec/modules/quantcastBidAdapter_spec.js | 27 ++++++++++++++----- 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/modules/quantcastBidAdapter.js b/modules/quantcastBidAdapter.js index 347ebc2ff55..e9541edb534 100644 --- a/modules/quantcastBidAdapter.js +++ b/modules/quantcastBidAdapter.js @@ -1,6 +1,7 @@ import * as utils from '../src/utils.js'; import { ajax } from '../src/ajax.js'; import { config } from '../src/config.js'; +import { getStorageManager } from '../src/storageManager.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import find from 'core-js-pure/features/array/find.js'; @@ -18,6 +19,9 @@ export const QUANTCAST_TEST_PUBLISHER = 'test-publisher'; export const QUANTCAST_TTL = 4; export const QUANTCAST_PROTOCOL = 'https'; export const QUANTCAST_PORT = '8443'; +export const QUANTCAST_FPA = '__qca'; + +export const storage = getStorageManager(QUANTCAST_VENDOR_ID, BIDDER_CODE); function makeVideoImp(bid) { const video = {}; @@ -101,6 +105,11 @@ function checkTCFv2(tcData) { return !!(vendorConsent && purposeConsent); } +function getQuantcastFPA() { + let fpa = storage.getCookie(QUANTCAST_FPA) + return fpa || '' +} + let hasUserSynced = false; /** @@ -109,7 +118,7 @@ let hasUserSynced = false; */ export const spec = { code: BIDDER_CODE, - GVLID: 11, + GVLID: QUANTCAST_VENDOR_ID, supportedMediaTypes: ['banner', 'video'], /** @@ -189,7 +198,8 @@ export const spec = { uspSignal: uspConsent ? 1 : 0, uspConsent, coppa: config.getConfig('coppa') === true ? 1 : 0, - prebidJsVersion: '$prebid.version$' + prebidJsVersion: '$prebid.version$', + fpa: getQuantcastFPA() }; const data = JSON.stringify(requestData); diff --git a/test/spec/modules/quantcastBidAdapter_spec.js b/test/spec/modules/quantcastBidAdapter_spec.js index caa554c8cd8..5b4e7963e60 100644 --- a/test/spec/modules/quantcastBidAdapter_spec.js +++ b/test/spec/modules/quantcastBidAdapter_spec.js @@ -7,7 +7,8 @@ import { QUANTCAST_TEST_PUBLISHER, QUANTCAST_PROTOCOL, QUANTCAST_PORT, - spec as qcSpec + spec as qcSpec, + storage } from '../../../modules/quantcastBidAdapter.js'; import { newBidder } from '../../../src/adapters/bidderFactory.js'; import { parseUrl } from 'src/utils.js'; @@ -42,6 +43,8 @@ describe('Quantcast adapter', function () { canonicalUrl: 'http://example.com/hello.html' } }; + + storage.setCookie('__qca', '', 'Thu, 01 Jan 1970 00:00:00 GMT'); }); function setupVideoBidRequest(videoParams) { @@ -140,7 +143,8 @@ describe('Quantcast adapter', function () { gdprSignal: 0, uspSignal: 0, coppa: 0, - prebidJsVersion: '$prebid.version$' + prebidJsVersion: '$prebid.version$', + fpa: '' }; it('sends banner bid requests contains all the required parameters', function () { @@ -208,7 +212,8 @@ describe('Quantcast adapter', function () { gdprSignal: 0, uspSignal: 0, coppa: 0, - prebidJsVersion: '$prebid.version$' + prebidJsVersion: '$prebid.version$', + fpa: '' }; expect(requests[0].data).to.equal(JSON.stringify(expectedVideoBidRequest)); @@ -244,7 +249,8 @@ describe('Quantcast adapter', function () { gdprSignal: 0, uspSignal: 0, coppa: 0, - prebidJsVersion: '$prebid.version$' + prebidJsVersion: '$prebid.version$', + fpa: '' }; expect(requests[0].data).to.equal(JSON.stringify(expectedVideoBidRequest)); @@ -276,7 +282,8 @@ describe('Quantcast adapter', function () { gdprSignal: 0, uspSignal: 0, coppa: 0, - prebidJsVersion: '$prebid.version$' + prebidJsVersion: '$prebid.version$', + fpa: '' }; expect(requests[0].data).to.equal(JSON.stringify(expectedVideoBidRequest)); @@ -340,7 +347,8 @@ describe('Quantcast adapter', function () { gdprSignal: 0, uspSignal: 0, coppa: 0, - prebidJsVersion: '$prebid.version$' + prebidJsVersion: '$prebid.version$', + fpa: '' }; expect(requests[0].data).to.equal(JSON.stringify(expectedBidRequest)); @@ -584,6 +592,13 @@ describe('Quantcast adapter', function () { expect(parsed.uspConsent).to.equal('consentString'); }); + it('propagates Quantcast first-party cookie (fpa)', function() { + storage.setCookie('__qca', 'P0-TestFPA'); + const requests = qcSpec.buildRequests([bidRequest], bidderRequest); + const parsed = JSON.parse(requests[0].data); + expect(parsed.fpa).to.equal('P0-TestFPA'); + }); + describe('propagates coppa', function() { let sandbox; beforeEach(() => { From fe7acf8a62e9f253c66a23358e4f5bc7906a1536 Mon Sep 17 00:00:00 2001 From: Scott Menzer Date: Tue, 17 Nov 2020 06:33:57 +0100 Subject: [PATCH 017/103] improve console ogging of user id module by listing all user id modules that have been enabled (#5975) --- modules/userId/index.js | 2 +- test/spec/modules/userId_spec.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/userId/index.js b/modules/userId/index.js index 9ef4da0f96f..0923a92f516 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -667,7 +667,7 @@ function updateSubmodules() { if (!addedUserIdHook && submodules.length) { // priority value 40 will load after consentManagement with a priority of 50 getGlobal().requestBids.before(requestBidsHook, 40); - utils.logInfo(`${MODULE_NAME} - usersync config updated for ${submodules.length} submodules`); + utils.logInfo(`${MODULE_NAME} - usersync config updated for ${submodules.length} submodules: `, submodules.map(a => a.submodule.name)); addedUserIdHook = true; } } diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index d5ed96a5bc1..887e1f45640 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -443,7 +443,7 @@ describe('User ID', function () { setSubmoduleRegistry([pubCommonIdSubmodule]); init(config); config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'])); - expect(utils.logInfo.args[0][0]).to.exist.and.to.equal('User ID - usersync config updated for 1 submodules'); + expect(utils.logInfo.args[0][0]).to.exist.and.to.contain('User ID - usersync config updated for 1 submodules'); }); }); @@ -506,7 +506,7 @@ describe('User ID', function () { init(config); config.setConfig(getConfigMock(['unifiedId', 'unifiedid', 'cookie'])); - expect(utils.logInfo.args[0][0]).to.exist.and.to.equal('User ID - usersync config updated for 1 submodules'); + expect(utils.logInfo.args[0][0]).to.exist.and.to.contain('User ID - usersync config updated for 1 submodules'); }); it('config with 13 configurations should result in 13 submodules add', function () { @@ -553,7 +553,7 @@ describe('User ID', function () { }] } }); - expect(utils.logInfo.args[0][0]).to.exist.and.to.equal('User ID - usersync config updated for 13 submodules'); + expect(utils.logInfo.args[0][0]).to.exist.and.to.contain('User ID - usersync config updated for 13 submodules'); }); it('config syncDelay updates module correctly', function () { From f6e34657e2b1347677b87afbdadd73a6195b1cb3 Mon Sep 17 00:00:00 2001 From: John Salis Date: Tue, 17 Nov 2020 01:00:18 -0500 Subject: [PATCH 018/103] Add IdentityLink support to Beachfront adapter (#5977) * add IdentityLink support to beachfront adapter * bump adapter version Co-authored-by: John Salis --- modules/beachfrontBidAdapter.js | 52 ++++++++++++++----- .../spec/modules/beachfrontBidAdapter_spec.js | 28 ++++++++++ 2 files changed, 66 insertions(+), 14 deletions(-) diff --git a/modules/beachfrontBidAdapter.js b/modules/beachfrontBidAdapter.js index f0e28956e5d..5f0a4b03a04 100644 --- a/modules/beachfrontBidAdapter.js +++ b/modules/beachfrontBidAdapter.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.12'; +const ADAPTER_VERSION = '1.13'; const ADAPTER_NAME = 'BFIO_PREBID'; const OUTSTREAM = 'outstream'; @@ -17,6 +17,11 @@ export const OUTSTREAM_SRC = 'https://player-cdn.beachfrontmedia.com/playerapi/l export const VIDEO_TARGETING = ['mimes', 'playbackmethod', 'maxduration', 'placement', 'skip', 'skipmin', 'skipafter']; export const DEFAULT_MIMES = ['video/mp4', 'application/javascript']; +export const SUPPORTED_USER_IDS = [ + { key: 'tdid', source: 'adserver.org', rtiPartner: 'TDID', queryParam: 'tdid' }, + { key: 'idl_env', source: 'liveramp.com', rtiPartner: 'idl', queryParam: 'idl' } +]; + let appId = ''; export const spec = { @@ -257,6 +262,29 @@ function getTopWindowReferrer() { } } +function getEids(bid) { + return SUPPORTED_USER_IDS + .map(getUserId(bid)) + .filter(x => x); +} + +function getUserId(bid) { + return ({ key, source, rtiPartner }) => { + let id = bid.userId && bid.userId[key]; + return id ? formatEid(id, source, rtiPartner) : null; + }; +} + +function formatEid(id, source, rtiPartner) { + return { + source, + uids: [{ + id, + ext: { rtiPartner } + }] + }; +} + function getVideoTargetingParams(bid) { const result = {}; const excludeProps = ['playerSize', 'context', 'w', 'h']; @@ -281,6 +309,7 @@ function createVideoRequestData(bid, bidderRequest) { let bidfloor = getVideoBidParam(bid, 'bidfloor'); let tagid = getVideoBidParam(bid, 'tagid'); let topLocation = getTopWindowLocation(bidderRequest); + let eids = getEids(bid); let payload = { isPrebid: true, appId: appId, @@ -329,16 +358,8 @@ function createVideoRequestData(bid, bidderRequest) { payload.user.ext.consent = consentString; } - if (bid.userId && bid.userId.tdid) { - payload.user.ext.eids = [{ - source: 'adserver.org', - uids: [{ - id: bid.userId.tdid, - ext: { - rtiPartner: 'TDID' - } - }] - }]; + if (eids.length > 0) { + payload.user.ext.eids = eids; } let connection = navigator.connection || navigator.webkitConnection; @@ -385,9 +406,12 @@ function createBannerRequestData(bids, bidderRequest) { payload.gdprConsent = consentString; } - if (bids[0] && bids[0].userId && bids[0].userId.tdid) { - payload.tdid = bids[0].userId.tdid; - } + SUPPORTED_USER_IDS.forEach(({ key, queryParam }) => { + let id = bids[0] && bids[0].userId && bids[0].userId[key]; + if (id) { + payload[queryParam] = id; + } + }); return payload; } diff --git a/test/spec/modules/beachfrontBidAdapter_spec.js b/test/spec/modules/beachfrontBidAdapter_spec.js index 587596eaa5c..aa952d088a7 100644 --- a/test/spec/modules/beachfrontBidAdapter_spec.js +++ b/test/spec/modules/beachfrontBidAdapter_spec.js @@ -295,6 +295,24 @@ describe('BeachfrontAdapter', function () { }] }); }); + + it('must add the IdentityLink ID to the request', () => { + const idl_env = '4321'; + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { video: {} }; + bidRequest.userId = { idl_env }; + const requests = spec.buildRequests([ bidRequest ]); + const data = requests[0].data; + expect(data.user.ext.eids[0]).to.deep.equal({ + source: 'liveramp.com', + uids: [{ + id: idl_env, + ext: { + rtiPartner: 'idl' + } + }] + }); + }); }); describe('for banner bids', function () { @@ -435,6 +453,16 @@ describe('BeachfrontAdapter', function () { const data = requests[0].data; expect(data.tdid).to.equal(tdid); }); + + it('must add the IdentityLink ID to the request', () => { + const idl_env = '4321'; + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { banner: {} }; + bidRequest.userId = { idl_env }; + const requests = spec.buildRequests([ bidRequest ]); + const data = requests[0].data; + expect(data.idl).to.equal(idl_env); + }); }); describe('for multi-format bids', function () { From f47287eef645c9c20b62aeaedf96a7893c602036 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Udi=20Talias=20=E2=9A=9B=EF=B8=8F?= Date: Tue, 17 Nov 2020 08:18:40 +0200 Subject: [PATCH 019/103] Vidazoo Adapter: Feature/spec-gvlid (#5980) * feat(module): multi size request * fix getUserSyncs added tests * update(module): package-lock.json from master * feat(module): expose spec gvlid Co-authored-by: roman --- modules/vidazooBidAdapter.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/vidazooBidAdapter.js b/modules/vidazooBidAdapter.js index 4b3b1767cec..7fc6e3a5395 100644 --- a/modules/vidazooBidAdapter.js +++ b/modules/vidazooBidAdapter.js @@ -3,7 +3,7 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; import { getStorageManager } from '../src/storageManager.js'; -const GLVID = 744; +const GVLID = 744; const DEFAULT_SUB_DOMAIN = 'prebid'; const BIDDER_CODE = 'vidazoo'; const BIDDER_VERSION = '1.0.0'; @@ -24,7 +24,7 @@ export const SUPPORTED_ID_SYSTEMS = { 'pubcid': 1, 'tdid': 1, }; -const storage = getStorageManager(GLVID); +const storage = getStorageManager(GVLID); export function createDomain(subDomain = DEFAULT_SUB_DOMAIN) { return `https://${subDomain}.cootlogix.com`; @@ -266,6 +266,7 @@ export function tryParseJSON(value) { export const spec = { code: BIDDER_CODE, + gvlid: GVLID, version: BIDDER_VERSION, supportedMediaTypes: [BANNER], isBidRequestValid, From 842f21c86fc3d7dba5bf5707140621c91243f3f6 Mon Sep 17 00:00:00 2001 From: jdwieland8282 Date: Tue, 17 Nov 2020 03:26:16 -0700 Subject: [PATCH 020/103] Update sharedIdSystem.js with GVLID (#5988) adding GVLID variable and spec definition, will come back and add it to getStorageManager once if I find that function referenced in the sharedid module. --- modules/sharedIdSystem.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/modules/sharedIdSystem.js b/modules/sharedIdSystem.js index cdd840c4f54..49cac46f1df 100644 --- a/modules/sharedIdSystem.js +++ b/modules/sharedIdSystem.js @@ -21,6 +21,7 @@ const TIME_MAX = Math.pow(2, 48) - 1; const TIME_LEN = 10; const RANDOM_LEN = 16; const id = factory(); +const GVLID = 887; /** * Constructs cookie value * @param value @@ -283,6 +284,11 @@ export const sharedIdSubmodule = { */ name: MODULE_NAME, + /** + * Vendor id of Prebid + * @type {Number} + */ + gvlid: GVLID, /** * decode the stored id value for passing to bid requests * @function From 18afadd0ed90cf16f4989f300ae9468623f56176 Mon Sep 17 00:00:00 2001 From: Elijah Valenciano Date: Tue, 17 Nov 2020 06:51:07 -0500 Subject: [PATCH 021/103] FreeWheel SSP - Added GDPR to userSync (#5969) --- modules/freewheel-sspBidAdapter.js | 13 ++++++++-- .../modules/freewheel-sspBidAdapter_spec.js | 26 +++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/modules/freewheel-sspBidAdapter.js b/modules/freewheel-sspBidAdapter.js index dce678362cb..53f490a0a3c 100644 --- a/modules/freewheel-sspBidAdapter.js +++ b/modules/freewheel-sspBidAdapter.js @@ -407,11 +407,20 @@ export const spec = { return bidResponses; }, - getUserSyncs: function(syncOptions) { + getUserSyncs: function(syncOptions, responses, gdprConsent, usPrivacy) { + var gdprParams = ''; + if (gdprConsent) { + if (typeof gdprConsent.gdprApplies === 'boolean') { + gdprParams = `?gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + gdprParams = `?gdpr_consent=${gdprConsent.consentString}`; + } + } + if (syncOptions && syncOptions.pixelEnabled) { return [{ type: 'image', - url: USER_SYNC_URL + url: USER_SYNC_URL + gdprParams }]; } else { return []; diff --git a/test/spec/modules/freewheel-sspBidAdapter_spec.js b/test/spec/modules/freewheel-sspBidAdapter_spec.js index 3047b635d13..c44d7908ba8 100644 --- a/test/spec/modules/freewheel-sspBidAdapter_spec.js +++ b/test/spec/modules/freewheel-sspBidAdapter_spec.js @@ -152,6 +152,19 @@ describe('freewheelSSP BidAdapter Test', () => { expect(payload.playerSize).to.equal('300x600'); expect(payload._fw_gdpr_consent).to.exist.and.to.be.a('string'); expect(payload._fw_gdpr_consent).to.equal(gdprConsentString); + + let gdprConsent = { + 'gdprApplies': true, + 'consentString': gdprConsentString + } + let syncOptions = { + 'pixelEnabled': true + } + const userSyncs = spec.getUserSyncs(syncOptions, null, gdprConsent, null); + expect(userSyncs).to.deep.equal([{ + type: 'image', + url: 'https://ads.stickyadstv.com/auto-user-sync?gdpr=1&gdpr_consent=1FW-SSP-gdprConsent-' + }]); }); }) @@ -226,6 +239,19 @@ describe('freewheelSSP BidAdapter Test', () => { expect(payload.playerSize).to.equal('300x600'); expect(payload._fw_gdpr_consent).to.exist.and.to.be.a('string'); expect(payload._fw_gdpr_consent).to.equal(gdprConsentString); + + let gdprConsent = { + 'gdprApplies': true, + 'consentString': gdprConsentString + } + let syncOptions = { + 'pixelEnabled': true + } + const userSyncs = spec.getUserSyncs(syncOptions, null, gdprConsent, null); + expect(userSyncs).to.deep.equal([{ + type: 'image', + url: 'https://ads.stickyadstv.com/auto-user-sync?gdpr=1&gdpr_consent=1FW-SSP-gdprConsent-' + }]); }); }) From b73ab25d9a5d1d33a6a377bf2413b07fa1fca5bf Mon Sep 17 00:00:00 2001 From: relaido <63339139+relaido@users.noreply.github.com> Date: Tue, 17 Nov 2020 21:03:31 +0900 Subject: [PATCH 022/103] Fix request size validate (#5951) * add relaido adapter * remove event listener * fixed UserSyncs and e.data * fix conflicts * fixed request flow use cookie instead of local storage validate video size Co-authored-by: ishigami_shingo Co-authored-by: cmertv-sishigami Co-authored-by: t_bun --- modules/relaidoBidAdapter.js | 38 +++++---------------- test/spec/modules/relaidoBidAdapter_spec.js | 14 ++++---- 2 files changed, 15 insertions(+), 37 deletions(-) diff --git a/modules/relaidoBidAdapter.js b/modules/relaidoBidAdapter.js index bc2854de40b..c77afbe6ec5 100644 --- a/modules/relaidoBidAdapter.js +++ b/modules/relaidoBidAdapter.js @@ -6,17 +6,13 @@ import { getStorageManager } from '../src/storageManager.js'; const BIDDER_CODE = 'relaido'; const BIDDER_DOMAIN = 'api.relaido.jp'; -const ADAPTER_VERSION = '1.0.1'; +const ADAPTER_VERSION = '1.0.2'; const DEFAULT_TTL = 300; const UUID_KEY = 'relaido_uuid'; const storage = getStorageManager(); function isBidRequestValid(bid) { - if (!utils.isSafariBrowser() && !hasUuid()) { - utils.logWarn('uuid is not found.'); - return false; - } if (!utils.deepAccess(bid, 'params.placementId')) { utils.logWarn('placementId param is reqeuired.'); return false; @@ -64,7 +60,7 @@ function buildRequests(validBidRequests, bidderRequest) { }; if (hasVideoMediaType(bidRequest)) { - const playerSize = utils.deepAccess(bidRequest, 'mediaTypes.video.playerSize'); + const playerSize = getValidSizes(utils.deepAccess(bidRequest, 'mediaTypes.video.playerSize')); payload.width = playerSize[0][0]; payload.height = playerSize[0][1]; } else if (hasBannerMediaType(bidRequest)) { @@ -101,10 +97,6 @@ function interpretResponse(serverResponse, bidRequest) { return []; } - if (body.uuid) { - storage.setDataInLocalStorage(UUID_KEY, body.uuid); - } - const playerUrl = bidRequest.player || body.playerUrl; const mediaType = bidRequest.mediaType || VIDEO; @@ -141,7 +133,6 @@ function getUserSyncs(syncOptions, serverResponses) { if (serverResponses.length > 0) { syncUrl = utils.deepAccess(serverResponses, '0.body.syncUrl') || syncUrl; } - receiveMessage(); return [{ type: 'iframe', url: syncUrl @@ -219,17 +210,6 @@ function outstreamRender(bid) { }); } -function receiveMessage() { - window.addEventListener('message', setUuid); -} - -function setUuid(e) { - if (utils.isPlainObject(e.data) && e.data.relaido_uuid) { - storage.setDataInLocalStorage(UUID_KEY, e.data.relaido_uuid); - window.removeEventListener('message', setUuid); - } -} - function isBannerValid(bid) { if (!isMobile()) { return false; @@ -242,8 +222,8 @@ function isBannerValid(bid) { } function isVideoValid(bid) { - const playerSize = utils.deepAccess(bid, 'mediaTypes.video.playerSize'); - if (playerSize && utils.isArray(playerSize) && playerSize.length > 0) { + const playerSize = getValidSizes(utils.deepAccess(bid, 'mediaTypes.video.playerSize')); + if (playerSize.length > 0) { const context = utils.deepAccess(bid, 'mediaTypes.video.context'); if (context && context === 'outstream') { return true; @@ -252,12 +232,12 @@ function isVideoValid(bid) { return false; } -function hasUuid() { - return !!storage.getDataFromLocalStorage(UUID_KEY); -} - function getUuid() { - return storage.getDataFromLocalStorage(UUID_KEY) || ''; + const id = storage.getCookie(UUID_KEY) + if (id) return id; + const newId = utils.generateUUID(); + storage.setCookie(UUID_KEY, newId); + return newId; } export function isMobile() { diff --git a/test/spec/modules/relaidoBidAdapter_spec.js b/test/spec/modules/relaidoBidAdapter_spec.js index 42818232cda..65dcd9b7db7 100644 --- a/test/spec/modules/relaidoBidAdapter_spec.js +++ b/test/spec/modules/relaidoBidAdapter_spec.js @@ -1,16 +1,21 @@ import { expect } from 'chai'; import { spec } from 'modules/relaidoBidAdapter.js'; import * as utils from 'src/utils.js'; +import { getStorageManager } from '../../../src/storageManager.js'; const UUID_KEY = 'relaido_uuid'; const DEFAULT_USER_AGENT = window.navigator.userAgent; const MOBILE_USER_AGENT = 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.5 Mobile/15E148 Safari/604.1'; +const relaido_uuid = 'hogehoge'; const setUADefault = () => { window.navigator.__defineGetter__('userAgent', function () { return DEFAULT_USER_AGENT }) }; const setUAMobile = () => { window.navigator.__defineGetter__('userAgent', function () { return MOBILE_USER_AGENT }) }; +const storage = getStorageManager(); +storage.setCookie(UUID_KEY, relaido_uuid); + describe('RelaidoAdapter', function () { - const relaido_uuid = 'hogehoge'; + window.document.cookie = `${UUID_KEY}=${relaido_uuid}` let bidRequest; let bidderRequest; let serverResponse; @@ -65,7 +70,6 @@ describe('RelaidoAdapter', function () { height: bidRequest.mediaTypes.video.playerSize[0][1], mediaType: 'video', }; - localStorage.setItem(UUID_KEY, relaido_uuid); }); describe('spec.isBidRequestValid', function () { @@ -140,12 +144,6 @@ describe('RelaidoAdapter', function () { setUADefault(); }); - it('should return false when the uuid are missing', function () { - localStorage.removeItem(UUID_KEY); - const result = !!(utils.isSafariBrowser()); - expect(spec.isBidRequestValid(bidRequest)).to.equal(result); - }); - it('should return false when the placementId params are missing', function () { bidRequest.params.placementId = undefined; expect(spec.isBidRequestValid(bidRequest)).to.equal(false); From 96989ca4b8d36917e87caec1208dbf0a92e87537 Mon Sep 17 00:00:00 2001 From: Robert Ray Martinez III Date: Tue, 17 Nov 2020 06:29:42 -0800 Subject: [PATCH 023/103] Prebid Server Bid Adapter: Expose errors and server response times always (#5866) * Expose pbs reported errors Expose serverLatencyMillis always * clean up logic for other weird edge case * review comment for make function * not sure how parenthesis got there --- modules/prebidServerBidAdapter/index.js | 17 ++++++++++ modules/rubiconAnalyticsAdapter.js | 32 ++++++++++++++++--- .../modules/rubiconAnalyticsAdapter_spec.js | 1 + 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index b153d0bf8db..7c7962781d2 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -369,6 +369,18 @@ function addWurl(auctionId, adId, wurl) { } } +function getPbsResponseData(bidderRequests, response, pbsName, pbjsName) { + const bidderValues = utils.deepAccess(response, `ext.${pbsName}`); + if (bidderValues) { + Object.keys(bidderValues).forEach(bidder => { + let biddersReq = find(bidderRequests, bidderReq => bidderReq.bidderCode === bidder); + if (biddersReq) { + biddersReq[pbjsName] = bidderValues[bidder]; + } + }); + } +} + /** * @param {string} auctionId * @param {string} adId generated value set to bidObject.adId by bidderFactory Bid() @@ -676,6 +688,9 @@ const OPEN_RTB_PROTOCOL = { interpretResponse(response, bidderRequests) { const bids = []; + [['errors', 'serverErrors'], ['responsetimemillis', 'serverResponseTimeMs']] + .forEach(info => getPbsResponseData(bidderRequests, response, info[0], info[1])) + if (response.seatbid) { // a seatbid object contains a `bid` array and a `seat` string response.seatbid.forEach(seatbid => { @@ -698,6 +713,8 @@ const OPEN_RTB_PROTOCOL = { bidObject.cpm = cpm; + // temporarily leaving attaching it to each bidResponse so no breaking change + // BUT: this is a flat map, so it should be only attached to bidderRequest, a the change above does let serverResponseTimeMs = utils.deepAccess(response, ['ext', 'responsetimemillis', seatbid.seat].join('.')); if (bidRequest && serverResponseTimeMs) { bidRequest.serverResponseTimeMs = serverResponseTimeMs; diff --git a/modules/rubiconAnalyticsAdapter.js b/modules/rubiconAnalyticsAdapter.js index 4011232ae3b..6f00e9536d9 100644 --- a/modules/rubiconAnalyticsAdapter.js +++ b/modules/rubiconAnalyticsAdapter.js @@ -13,6 +13,14 @@ const COOKIE_NAME = 'rpaSession'; const LAST_SEEN_EXPIRE_TIME = 1800000; // 30 mins const END_EXPIRE_TIME = 21600000; // 6 hours +const pbsErrorMap = { + 1: 'timeout-error', + 2: 'input-error', + 3: 'connect-error', + 4: 'request-error', + 999: 'generic-error' +} + let prebidGlobal = getGlobal(); const { EVENTS: { @@ -631,10 +639,22 @@ let rubiconAdapter = Object.assign({}, baseAdapter, { bid.bidResponse = parseBidResponse(args, bid.bidResponse); break; case BIDDER_DONE: + const serverError = utils.deepAccess(args, 'serverErrors.0'); + const serverResponseTimeMs = args.serverResponseTimeMs; args.bids.forEach(bid => { let cachedBid = cache.auctions[bid.auctionId].bids[bid.bidId || bid.requestId]; if (typeof bid.serverResponseTimeMs !== 'undefined') { cachedBid.serverLatencyMillis = bid.serverResponseTimeMs; + } else if (serverResponseTimeMs && bid.source === 's2s') { + cachedBid.serverLatencyMillis = serverResponseTimeMs; + } + // if PBS said we had an error, and this bid has not been processed by BID_RESPONSE YET + if (serverError && (!cachedBid.status || ['no-bid', 'error'].indexOf(cachedBid.status) !== -1)) { + cachedBid.status = 'error'; + cachedBid.error = { + code: pbsErrorMap[serverError.code] || pbsErrorMap[999], + description: serverError.message + } } if (!cachedBid.status) { cachedBid.status = 'no-bid'; @@ -675,10 +695,14 @@ let rubiconAdapter = Object.assign({}, baseAdapter, { args.forEach(badBid => { let auctionCache = cache.auctions[badBid.auctionId]; let bid = auctionCache.bids[badBid.bidId || badBid.requestId]; - bid.status = 'error'; - bid.error = { - code: 'timeout-error' - }; + // might be set already by bidder-done, so do not overwrite + if (bid.status !== 'error') { + bid.status = 'error'; + bid.error = { + code: 'timeout-error', + message: 'marked by prebid.js as timeout' // will help us diff if timeout was set by PBS or PBJS + }; + } }); break; } diff --git a/test/spec/modules/rubiconAnalyticsAdapter_spec.js b/test/spec/modules/rubiconAnalyticsAdapter_spec.js index 7c552570da6..d65083ce480 100644 --- a/test/spec/modules/rubiconAnalyticsAdapter_spec.js +++ b/test/spec/modules/rubiconAnalyticsAdapter_spec.js @@ -310,6 +310,7 @@ const MOCK = { ], BIDDER_DONE: { 'bidderCode': 'rubicon', + 'serverResponseTimeMs': 42, 'bids': [ BID, Object.assign({}, BID2, { From 71e9cc7b48b736ab6671acf6a5ad7c17ac4460e5 Mon Sep 17 00:00:00 2001 From: Ignat Khaylov Date: Wed, 18 Nov 2020 09:29:05 +0300 Subject: [PATCH 024/103] Between: schain support was added (#5982) Co-authored-by: Ignat Khaylov --- modules/betweenBidAdapter.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/modules/betweenBidAdapter.js b/modules/betweenBidAdapter.js index fb3fcdb8d89..0ed05717391 100644 --- a/modules/betweenBidAdapter.js +++ b/modules/betweenBidAdapter.js @@ -59,6 +59,10 @@ export const spec = { } } + if (i.schain) { + params.schain = encodeToBase64WebSafe(JSON.stringify(i.schain)); + } + if (refInfo && refInfo.referer) params.ref = refInfo.referer; if (gdprConsent) { @@ -166,6 +170,10 @@ function getTz() { return new Date().getTimezoneOffset(); } +function encodeToBase64WebSafe(string) { + return btoa(string).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); +} + /* function get_pubdata(adds) { if (adds !== undefined && adds.pubdata !== undefined) { From efbc9d308b6e6451d234c7548e6eb1821044e413 Mon Sep 17 00:00:00 2001 From: Robert Ray Martinez III Date: Tue, 17 Nov 2020 22:38:58 -0800 Subject: [PATCH 025/103] fix source lowercase bug (#5989) --- modules/rubiconAnalyticsAdapter.js | 4 ++-- test/spec/modules/rubiconAnalyticsAdapter_spec.js | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/modules/rubiconAnalyticsAdapter.js b/modules/rubiconAnalyticsAdapter.js index 6f00e9536d9..85b6596ba12 100644 --- a/modules/rubiconAnalyticsAdapter.js +++ b/modules/rubiconAnalyticsAdapter.js @@ -125,7 +125,7 @@ function sendMessage(auctionId, bidWonId) { if (source) { return source; } - return serverConfig && Array.isArray(serverConfig.bidders) && serverConfig.bidders.indexOf(bid.bidder) !== -1 + return serverConfig && Array.isArray(serverConfig.bidders) && serverConfig.bidders.some(s2sBidder => s2sBidder.toLowerCase() === bid.bidder) !== -1 ? 'server' : 'client' }, 'clientLatencyMillis', @@ -533,7 +533,7 @@ let rubiconAdapter = Object.assign({}, baseAdapter, { 'bidder', bidder => bidder.toLowerCase(), 'bidId', 'status', () => 'no-bid', // default a bid to no-bid until response is recieved or bid is timed out - 'finalSource as source', + 'source', () => formatSource(bid.src), 'params', (params, bid) => { switch (bid.bidder) { // specify bidder params we want here diff --git a/test/spec/modules/rubiconAnalyticsAdapter_spec.js b/test/spec/modules/rubiconAnalyticsAdapter_spec.js index d65083ce480..9e343d07dd5 100644 --- a/test/spec/modules/rubiconAnalyticsAdapter_spec.js +++ b/test/spec/modules/rubiconAnalyticsAdapter_spec.js @@ -255,7 +255,8 @@ const MOCK = { 'sizes': [[640, 480]], 'bidId': '2ecff0db240757', 'bidderRequestId': '1be65d7958826a', - 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa' + 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', + 'src': 'client' }, { 'bidder': 'rubicon', @@ -279,7 +280,8 @@ const MOCK = { 'sizes': [[1000, 300], [970, 250], [728, 90]], 'bidId': '3bd4ebb1c900e2', 'bidderRequestId': '1be65d7958826a', - 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa' + 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', + 'src': 's2s' } ], 'auctionStart': 1519149536560, From da87e57527ea3c26c6678f1a223794555706b4fd Mon Sep 17 00:00:00 2001 From: SmartyAdman <59048845+SmartyAdman@users.noreply.github.com> Date: Wed, 18 Nov 2020 08:44:49 +0200 Subject: [PATCH 026/103] Add consent to sync url (#5981) * Add Adman bid adapter * Add supportedMediaTypes property * Update ADman Media bidder adapter * Remove console.log * Fix typo * revert package-json.lock * Delete package-lock.json * back to original package-lock.json * catch pbjs error * catch pbjs error * catch pbjs error * log * remove eu url * remove eu url * remove eu url * remove eu url * remove eu url * Update admanBidAdapter.js add consnet to sync url * Update admanBidAdapter.js fix import * Update admanBidAdapter.js lint fix * Update admanBidAdapter.js lint fix * Update admanBidAdapter.js check consent object data availability Co-authored-by: minoru katogi Co-authored-by: minoru katogi Co-authored-by: ADman Media Co-authored-by: SmartyAdman --- modules/admanBidAdapter.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/modules/admanBidAdapter.js b/modules/admanBidAdapter.js index 5dc3412ee66..2e4091e7a24 100644 --- a/modules/admanBidAdapter.js +++ b/modules/admanBidAdapter.js @@ -95,10 +95,21 @@ export const spec = { return response; }, - getUserSyncs: (syncOptions, serverResponses) => { + getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { + let syncUrl = URL_SYNC + 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}`; + } return [{ type: 'image', - url: URL_SYNC + url: syncUrl }]; } From c308898cddc80d7669c3d2d318e13ef5cb366780 Mon Sep 17 00:00:00 2001 From: Scott Menzer Date: Wed, 18 Nov 2020 07:58:56 +0100 Subject: [PATCH 027/103] add provider as an option in id5 config params to identity prebid identity wrappers (#5983) --- modules/id5IdSystem.js | 1 + modules/id5IdSystem.md | 3 ++- test/spec/modules/id5IdSystem_spec.js | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/id5IdSystem.js b/modules/id5IdSystem.js index 5a1fc69a758..a1596e96fcd 100644 --- a/modules/id5IdSystem.js +++ b/modules/id5IdSystem.js @@ -89,6 +89,7 @@ export const id5IdSubmodule = { 'nbPage': incrementNb(config.params.partner), 'o': 'pbjs', 'pd': config.params.pd || '', + 'provider': config.params.provider || '', 'rf': referer.referer, 's': signature, 'top': referer.reachedTop ? 1 : 0, diff --git a/modules/id5IdSystem.md b/modules/id5IdSystem.md index 80ba451b235..e5e3969c19c 100644 --- a/modules/id5IdSystem.md +++ b/modules/id5IdSystem.md @@ -45,10 +45,11 @@ pbjs.setConfig({ | params | Required | Object | Details for the ID5 Universal ID. | | | params.partner | Required | Number | This is the ID5 Partner Number obtained from registering with ID5. | `173` | | params.pd | Optional | String | Publisher-supplied data used for linking ID5 IDs across domains. See [our documentation](https://wiki.id5.io/x/BIAZ) 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` | | storage | Required | Object | Storage settings for how the User ID module will cache the ID5 ID locally | | | storage.type | Required | String | This is where the results of the user ID will be stored. ID5 **requires** `"html5"`. | `"html5"` | | storage.name | Required | String | The name of the local storage where the user ID will be stored. ID5 **requires** `"id5id"`. | `"id5id"` | | storage.expires | Optional | Integer | How long (in days) the user ID information will be stored. ID5 recommends `90`. | `90` | | storage.refreshInSeconds | Optional | Integer | How many seconds until the ID5 ID will be refreshed. ID5 strongly recommends 8 hours between refreshes | `8*3600` | -**ATTENTION:** As of Prebid.js v4.14.0, ID5 requires `storage.type` to be `"html5"` and `storage.name` to be `"id5id"`. Using other values will display a warning today, but in an upcoming release, it will prevent the ID5 module from loading. This change is to ensure the ID5 module in Prebid.js interoperates properly with the [ID5 API](https://github.com/id5io/id5-api.js) and to reduce the size of publishers' first-party cookies that are sent to their web servers. If you have any questions, please reach out to us at [prebid@id5.io](mailto:prebid@id5.io). \ No newline at end of file +**ATTENTION:** As of Prebid.js v4.14.0, ID5 requires `storage.type` to be `"html5"` and `storage.name` to be `"id5id"`. Using other values will display a warning today, but in an upcoming release, it will prevent the ID5 module from loading. This change is to ensure the ID5 module in Prebid.js interoperates properly with the [ID5 API](https://github.com/id5io/id5-api.js) and to reduce the size of publishers' first-party cookies that are sent to their web servers. If you have any questions, please reach out to us at [prebid@id5.io](mailto:prebid@id5.io). diff --git a/test/spec/modules/id5IdSystem_spec.js b/test/spec/modules/id5IdSystem_spec.js index bfc41e5f5e8..adffca6dbe5 100644 --- a/test/spec/modules/id5IdSystem_spec.js +++ b/test/spec/modules/id5IdSystem_spec.js @@ -141,6 +141,7 @@ describe('ID5 ID System', function() { expect(requestBody.o).to.eq('pbjs'); expect(requestBody.pd).to.eq(''); expect(requestBody.s).to.eq(''); + expect(requestBody.provider).to.eq(''); expect(requestBody.v).to.eq('$prebid.version$'); request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); From 8340be830911e904b457950861945d6a34bbeed7 Mon Sep 17 00:00:00 2001 From: Eric Harper Date: Wed, 18 Nov 2020 15:14:06 -0500 Subject: [PATCH 028/103] Prebid 4.17.0 Release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4b62de09e1f..d957c0901c3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.17.0-pre", + "version": "4.17.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From a84b4e994c7c64c4adabba4fa2215df8e64f23b8 Mon Sep 17 00:00:00 2001 From: Eric Harper Date: Wed, 18 Nov 2020 15:42:30 -0500 Subject: [PATCH 029/103] Increment pre version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d957c0901c3..48963f36c76 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.17.0", + "version": "4.18.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 57fb14ccc2d0f3cf9113d820f75a9f59645e66db Mon Sep 17 00:00:00 2001 From: Dmitriy Labuzov Date: Thu, 19 Nov 2020 11:16:55 +0300 Subject: [PATCH 030/103] Instream video support for Yieldmo adapter (#5973) Co-authored-by: Dmitriy Labuzov --- modules/yieldmoBidAdapter.js | 399 +++++++++++++++--- modules/yieldmoBidAdapter.md | 75 +++- test/spec/modules/yieldmoBidAdapter_spec.js | 438 ++++++++++---------- 3 files changed, 614 insertions(+), 298 deletions(-) diff --git a/modules/yieldmoBidAdapter.js b/modules/yieldmoBidAdapter.js index 08dc3189eda..05af0bf0d66 100644 --- a/modules/yieldmoBidAdapter.js +++ b/modules/yieldmoBidAdapter.js @@ -1,103 +1,133 @@ import * as utils from '../src/utils.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; +import includes from 'core-js-pure/features/array/includes'; const BIDDER_CODE = 'yieldmo'; const CURRENCY = 'USD'; const TIME_TO_LIVE = 300; const NET_REVENUE = true; -const SERVER_ENDPOINT = 'https://ads.yieldmo.com/exchange/prebid'; +const BANNER_SERVER_ENDPOINT = 'https://ads.yieldmo.com/exchange/prebid'; +const VIDEO_SERVER_ENDPOINT = 'https://ads.yieldmo.com/exchange/prebidvideo'; +const OPENRTB_VIDEO_BIDPARAMS = ['placement', 'startdelay', 'skipafter', + 'protocols', 'api', 'playbackmethod', 'maxduration', 'minduration', 'pos']; +const OPENRTB_VIDEO_SITEPARAMS = ['name', 'domain', 'cat', 'keywords']; const localWindow = utils.getWindowTop(); export const spec = { code: BIDDER_CODE, - supportedMediaTypes: ['banner'], + supportedMediaTypes: [BANNER, VIDEO], + /** * Determines whether or not the given bid request is valid. * @param {object} bid, bid to validate * @return boolean, true if valid, otherwise false */ isBidRequestValid: function (bid) { - return !!(bid && bid.adUnitCode && bid.bidId); + return !!(bid && bid.adUnitCode && bid.bidId && (hasBannerMediaType(bid) || hasVideoMediaType(bid)) && + validateVideoParams(bid)); }, + /** * Make a server request from the list of BidRequests. * * @param {BidRequest[]} bidRequests A non-empty list of bid requests which should be sent to the Server. + * @param {BidderRequest} bidderRequest bidder request object. * @return ServerRequest Info describing the request to the server. */ buildRequests: function (bidRequests, bidderRequest) { - let serverRequest = { - pbav: '$prebid.version$', - p: [], - page_url: bidderRequest.refererInfo.referer, - bust: new Date().getTime().toString(), - pr: bidderRequest.refererInfo.referer, - scrd: localWindow.devicePixelRatio || 0, - dnt: getDNT(), - description: getPageDescription(), - title: localWindow.document.title || '', - w: localWindow.innerWidth, - h: localWindow.innerHeight, - userConsent: JSON.stringify({ - // case of undefined, stringify will remove param - gdprApplies: utils.deepAccess(bidderRequest, 'gdprConsent.gdprApplies') || '', - cmp: utils.deepAccess(bidderRequest, 'gdprConsent.consentString') || '' - }), - us_privacy: utils.deepAccess(bidderRequest, 'uspConsent') || '' - }; - - const mtp = window.navigator.maxTouchPoints; - if (mtp) { - serverRequest.mtp = mtp; - } + const bannerBidRequests = bidRequests.filter(request => hasBannerMediaType(request)); + const videoBidRequests = bidRequests.filter(request => hasVideoMediaType(request)); + + let serverRequests = []; + if (bannerBidRequests.length > 0) { + let serverRequest = { + pbav: '$prebid.version$', + p: [], + page_url: bidderRequest.refererInfo.referer, + bust: new Date().getTime().toString(), + pr: bidderRequest.refererInfo.referer, + scrd: localWindow.devicePixelRatio || 0, + dnt: getDNT(), + description: getPageDescription(), + title: localWindow.document.title || '', + w: localWindow.innerWidth, + h: localWindow.innerHeight, + userConsent: JSON.stringify({ + // case of undefined, stringify will remove param + gdprApplies: utils.deepAccess(bidderRequest, 'gdprConsent.gdprApplies') || '', + cmp: utils.deepAccess(bidderRequest, 'gdprConsent.consentString') || '' + }), + us_privacy: utils.deepAccess(bidderRequest, 'uspConsent') || '' + }; - bidRequests.forEach(request => { - serverRequest.p.push(addPlacement(request)); - const pubcid = getId(request, 'pubcid'); - if (pubcid) { - serverRequest.pubcid = pubcid; - } else if (request.crumbs) { - if (request.crumbs.pubcid) { + const mtp = window.navigator.maxTouchPoints; + if (mtp) { + serverRequest.mtp = mtp; + } + + bannerBidRequests.forEach(request => { + serverRequest.p.push(addPlacement(request)); + const pubcid = getId(request, 'pubcid'); + if (pubcid) { + serverRequest.pubcid = pubcid; + } else if (request.crumbs && request.crumbs.pubcid) { serverRequest.pubcid = request.crumbs.pubcid; } - } - const tdid = getId(request, 'tdid'); - if (tdid) { - serverRequest.tdid = tdid; - } - const criteoId = getId(request, 'criteoId'); - if (criteoId) { - serverRequest.cri_prebid = criteoId; - } - if (request.schain) { - serverRequest.schain = - JSON.stringify(request.schain); - } - }); - serverRequest.p = '[' + serverRequest.p.toString() + ']'; - return { - method: 'GET', - url: SERVER_ENDPOINT, - data: serverRequest - }; + const tdid = getId(request, 'tdid'); + if (tdid) { + serverRequest.tdid = tdid; + } + const criteoId = getId(request, 'criteoId'); + if (criteoId) { + serverRequest.cri_prebid = criteoId; + } + if (request.schain) { + serverRequest.schain = JSON.stringify(request.schain); + } + }); + serverRequest.p = '[' + serverRequest.p.toString() + ']'; + serverRequests.push({ + method: 'GET', + url: BANNER_SERVER_ENDPOINT, + data: serverRequest + }); + } + + if (videoBidRequests.length > 0) { + const serverRequest = openRtbRequest(videoBidRequests, bidderRequest); + serverRequests.push({ + method: 'POST', + url: VIDEO_SERVER_ENDPOINT, + data: serverRequest + }); + } + return serverRequests; }, + /** * Makes Yieldmo Ad Server response compatible to Prebid specs - * @param serverResponse successful response from Ad Server + * @param {ServerResponse} serverResponse successful response from Ad Server + * @param {ServerRequest} bidRequest * @return {Bid[]} an array of bids */ - interpretResponse: function (serverResponse) { + interpretResponse: function (serverResponse, bidRequest) { let bids = []; - let data = serverResponse.body; + const data = serverResponse.body; if (data.length > 0) { data.forEach(response => { - if (response.cpm && response.cpm > 0) { - bids.push(createNewBid(response)); + if (response.cpm > 0) { + bids.push(createNewBannerBid(response)); } }); } + if (data.seatbid) { + const seatbids = data.seatbid.reduce((acc, seatBid) => acc.concat(seatBid.bid), []); + seatbids.forEach(bid => bids.push(createNewVideoBid(bid, bidRequest))); + } return bids; }, + getUserSyncs: function () { return []; } @@ -108,6 +138,20 @@ registerBidder(spec); * Helper Functions ***************************************/ +/** + * @param {BidRequest} bidRequest bid request + */ +function hasBannerMediaType(bidRequest) { + return !!utils.deepAccess(bidRequest, 'mediaTypes.banner'); +} + +/** + * @param {BidRequest} bidRequest bid request + */ +function hasVideoMediaType(bidRequest) { + return !!utils.deepAccess(bidRequest, 'mediaTypes.video'); +} + /** * Adds placement information to array * @param request bid request @@ -130,10 +174,10 @@ function addPlacement(request) { } /** - * creates a new bid with response information + * creates a new banner bid with response information * @param response server response */ -function createNewBid(response) { +function createNewBannerBid(response) { return { requestId: response['callback_id'], cpm: response.cpm, @@ -147,6 +191,27 @@ function createNewBid(response) { }; } +/** + * creates a new video bid with response information + * @param response openRTB server response + * @param bidRequest server request + */ +function createNewVideoBid(response, bidRequest) { + const imp = (utils.deepAccess(bidRequest, 'data.imp') || []).find(imp => imp.id === response.impid); + return { + requestId: imp.id, + cpm: response.price, + width: imp.video.w, + height: imp.video.h, + creativeId: response.crid || response.adid, + currency: CURRENCY, + netRevenue: NET_REVENUE, + mediaType: VIDEO, + ttl: TIME_TO_LIVE, + vastXml: response.adm + }; +} + /** * Detects whether dnt is true * @returns true if user enabled dnt @@ -179,3 +244,215 @@ function getPageDescription() { function getId(request, idType) { return (typeof utils.deepAccess(request, 'userId') === 'object') ? request.userId[idType] : undefined; } + +/** + * @param {BidRequest[]} bidRequests bid request object + * @param {BidderRequest} bidderRequest bidder request object + * @return Object OpenRTB request object + */ +function openRtbRequest(bidRequests, bidderRequest) { + let openRtbRequest = { + id: bidRequests[0].bidderRequestId, + at: 1, + imp: bidRequests.map(bidRequest => openRtbImpression(bidRequest)), + site: openRtbSite(bidRequests[0], bidderRequest), + device: openRtbDevice(), + badv: bidRequests[0].params.badv || [], + bcat: bidRequests[0].params.bcat || [], + ext: { + prebid: '$prebid.version$', + } + }; + + populateOpenRtbGdpr(openRtbRequest, bidderRequest); + + return openRtbRequest; +} + +/** + * @param {BidRequest} bidRequest bidder request object. + * @return Object OpenRTB's 'imp' (impression) object + */ +function openRtbImpression(bidRequest) { + const videoReq = utils.deepAccess(bidRequest, 'mediaTypes.video'); + const size = extractPlayerSize(bidRequest); + const imp = { + id: bidRequest.bidId, + tagid: bidRequest.adUnitCode, + bidfloor: bidRequest.params.bidfloor || 0, + ext: { + placement_id: bidRequest.params.placementId + }, + video: { + w: size[0], + h: size[1], + mimes: videoReq.mimes, + linearity: 1 + } + }; + + const videoParams = utils.deepAccess(bidRequest, 'params.video'); + Object.keys(videoParams) + .filter(param => includes(OPENRTB_VIDEO_BIDPARAMS, param)) + .forEach(param => imp.video[param] = videoParams[param]); + + if (videoParams.skippable) { + imp.video.skip = 1; + } + + return imp; +} + +/** + * @param {BidRequest} bidRequest bidder request object. + * @return [number, number] || null Player's width and height, or undefined otherwise. + */ +function extractPlayerSize(bidRequest) { + const sizeArr = utils.deepAccess(bidRequest, 'mediaTypes.video.playerSize'); + if (utils.isArrayOfNums(sizeArr, 2)) { + return sizeArr; + } else if (utils.isArray(sizeArr) && utils.isArrayOfNums(sizeArr[0], 2)) { + return sizeArr[0]; + } + return null; +} + +/** + * @param {BidRequest} bidRequest bid request object + * @param {BidderRequest} bidderRequest bidder request object + * @return Object OpenRTB's 'site' object + */ +function openRtbSite(bidRequest, bidderRequest) { + let result = {}; + + const loc = utils.parseUrl(utils.deepAccess(bidderRequest, 'refererInfo.referer')); + if (!utils.isEmpty(loc)) { + result.page = `${loc.protocol}://${loc.hostname}${loc.pathname}`; + } + + if (self === top && document.referrer) { + result.ref = document.referrer; + } + + const keywords = document.getElementsByTagName('meta')['keywords']; + if (keywords && keywords.content) { + result.keywords = keywords.content; + } + + const siteParams = utils.deepAccess(bidRequest, 'params.site'); + if (siteParams) { + Object.keys(siteParams) + .filter(param => includes(OPENRTB_VIDEO_SITEPARAMS, param)) + .forEach(param => result[param] = siteParams[param]); + } + return result; +} + +/** + * @return Object OpenRTB's 'device' object + */ +function openRtbDevice() { + return { + ua: navigator.userAgent, + language: (navigator.language || navigator.browserLanguage || navigator.userLanguage || navigator.systemLanguage), + }; +} + +/** + * Updates openRtbRequest with GDPR info from bidderRequest, if present. + * @param {Object} openRtbRequest OpenRTB's request to update. + * @param {BidderRequest} bidderRequest bidder request object. + */ +function populateOpenRtbGdpr(openRtbRequest, bidderRequest) { + const gdpr = bidderRequest.gdprConsent; + if (gdpr && 'gdprApplies' in gdpr) { + utils.deepSetValue(openRtbRequest, 'regs.ext.gdpr', gdpr.gdprApplies ? 1 : 0); + utils.deepSetValue(openRtbRequest, 'user.ext.consent', gdpr.consentString); + } + const uspConsent = utils.deepAccess(bidderRequest, 'uspConsent'); + if (uspConsent) { + utils.deepSetValue(openRtbRequest, 'regs.ext.us_privacy', uspConsent); + } +} + +/** + * Determines whether or not the given video bid request is valid. If it's not a video bid, returns true. + * @param {object} bid, bid to validate + * @return boolean, true if valid, otherwise false + */ +function validateVideoParams(bid) { + if (!hasVideoMediaType(bid)) { + return true; + } + + const paramRequired = (paramStr, value, conditionStr) => { + let error = `"${paramStr}" is required`; + if (conditionStr) { + error += ' when ' + conditionStr; + } + throw new Error(error); + } + + const paramInvalid = (paramStr, value, expectedStr) => { + expectedStr = expectedStr ? ', expected: ' + expectedStr : ''; + value = JSON.stringify(value); + throw new Error(`"${paramStr}"=${value} is invalid${expectedStr}`); + } + + const isDefined = val => typeof val !== 'undefined'; + const validate = (fieldPath, validateCb, errorCb, errorCbParam) => { + const value = utils.deepAccess(bid, fieldPath); + if (!validateCb(value)) { + errorCb(fieldPath, value, errorCbParam); + } + return value; + } + + try { + validate('params.placementId', val => !utils.isEmpty(val), paramRequired); + + validate('mediaTypes.video.playerSize', val => utils.isArrayOfNums(val, 2) || + (utils.isArray(val) && val.every(v => utils.isArrayOfNums(v, 2))), + paramInvalid, 'array of 2 integers, ex: [640,480] or [[640,480]]'); + + validate('mediaTypes.video.mimes', val => isDefined(val), paramRequired); + validate('mediaTypes.video.mimes', val => utils.isArray(val) && val.every(v => utils.isStr(v)), paramInvalid, + 'array of strings, ex: ["video/mp4"]'); + + validate('params.video', val => !utils.isEmpty(val), paramRequired); + + const placement = validate('params.video.placement', val => isDefined(val), paramRequired); + validate('params.video.placement', val => val >= 1 && val <= 5, paramInvalid); + if (placement === 1) { + validate('params.video.startdelay', val => isDefined(val), + (field, v) => paramRequired(field, v, 'placement == 1')); + validate('params.video.startdelay', val => utils.isNumber(val), paramInvalid, 'number, ex: 5'); + } + + validate('params.video.protocols', val => isDefined(val), paramRequired); + validate('params.video.protocols', val => utils.isArrayOfNums(val) && val.every(v => (v >= 1 && v <= 6)), + paramInvalid, 'array of numbers, ex: [2,3]'); + + validate('params.video.api', val => isDefined(val), paramRequired); + validate('params.video.api', val => utils.isArrayOfNums(val) && val.every(v => (v >= 1 && v <= 6)), + paramInvalid, 'array of numbers, ex: [2,3]'); + + validate('params.video.playbackmethod', val => !isDefined(val) || utils.isArrayOfNums(val), paramInvalid, + 'array of integers, ex: [2,6]'); + + validate('params.video.maxduration', val => isDefined(val), paramRequired); + validate('params.video.maxduration', val => utils.isInteger(val), paramInvalid); + validate('params.video.minduration', val => !isDefined(val) || utils.isNumber(val), paramInvalid); + validate('params.video.skippable', val => !isDefined(val) || utils.isBoolean(val), paramInvalid); + validate('params.video.skipafter', val => !isDefined(val) || utils.isNumber(val), paramInvalid); + validate('params.video.pos', val => !isDefined(val) || utils.isNumber(val), paramInvalid); + validate('params.badv', val => !isDefined(val) || utils.isArray(val), paramInvalid, + 'array of strings, ex: ["ford.com","pepsi.com"]'); + validate('params.bcat', val => !isDefined(val) || utils.isArray(val), paramInvalid, + 'array of strings, ex: ["IAB1-5","IAB1-6"]'); + return true; + } catch (e) { + utils.logError(e.message); + return false; + } +} diff --git a/modules/yieldmoBidAdapter.md b/modules/yieldmoBidAdapter.md index 0f86d2507d1..1b8b7b1b741 100644 --- a/modules/yieldmoBidAdapter.md +++ b/modules/yieldmoBidAdapter.md @@ -11,26 +11,61 @@ Note: Our ads will only render in mobile Connects to Yieldmo Ad Server for bids. -Yieldmo bid adapter supports Banner. +Yieldmo bid adapter supports Banner and Video. # Test Parameters + +## Banner + +Sample banner ad unit config: +```javascript +var adUnits = [{ // Banner adUnit + code: 'div-gpt-ad-1460505748561-0', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + } + }, + bids: [{ + bidder: 'yieldmo', + params: { + placementId: '1779781193098233305', // string with at most 19 characters (may include numbers only) + bidFloor: .28 // optional param + } + }] +}]; +``` + +## Video + +Sample instream video ad unit config: +```javascript +var adUnits = [{ // Video adUnit + code: 'div-video-ad-1234567890', + mediaTypes: { + video: { + playerSize: [640, 480], // required + context: 'instream', + mimes: ['video/mp4'] // required, array of strings + } + }, + bids: [{ + bidder: 'yieldmo', + params: { + placementId: '1524592390382976659', // required + video: { + placement: 1, // required, integer + maxduration: 30, // required, integer + minduration: 15, // optional, integer + pos: 1, // optional, integer + startdelay: 10, // required if placement == 1 + protocols: [2, 3], // required, array of integers + api: [2, 3], // required, array of integers + playbackmethod: [2,6], // required, array of integers + skippable: true, // optional, boolean + skipafter: 10 // optional, integer + } + } + }] +}]; ``` -var adUnits = [ - // Banner adUnit - { - code: 'div-gpt-ad-1460505748561-0', - mediaTypes: { - banner: { - sizes: [[300, 250], [300,600]], - } - } - bids: [{ - bidder: 'yieldmo', - params: { - placementId: '1779781193098233305', // string with at most 19 characters (may include numbers only) - bidFloor: .28 // optional param - } - }] - } -]; -``` \ No newline at end of file diff --git a/test/spec/modules/yieldmoBidAdapter_spec.js b/test/spec/modules/yieldmoBidAdapter_spec.js index caeb26266fe..deabef69093 100644 --- a/test/spec/modules/yieldmoBidAdapter_spec.js +++ b/test/spec/modules/yieldmoBidAdapter_spec.js @@ -1,19 +1,16 @@ import { expect } from 'chai'; import { spec } from 'modules/yieldmoBidAdapter.js'; -import { newBidder } from 'src/adapters/bidderFactory.js'; import * as utils from 'src/utils.js'; describe('YieldmoAdapter', function () { - const adapter = newBidder(spec); - const ENDPOINT = 'https://ads.yieldmo.com/exchange/prebid'; + const BANNER_ENDPOINT = 'https://ads.yieldmo.com/exchange/prebid'; + const VIDEO_ENDPOINT = 'https://ads.yieldmo.com/exchange/prebidvideo'; - let tdid = '8d146286-91d4-4958-aff4-7e489dd1abd6'; - let criteoId = 'aff4'; - - let bid = { + const mockBannerBid = (rootParams = {}, params = {}) => ({ bidder: 'yieldmo', params: { bidFloor: 0.1, + ...params, }, adUnitCode: 'adunit-code', mediaTypes: { @@ -31,15 +28,44 @@ describe('YieldmoAdapter', function () { pubcid: 'c604130c-0144-4b63-9bf2-c2bd8c8d86da', }, userId: { - tdid, + tdid: '8d146286-91d4-4958-aff4-7e489dd1abd6' + }, + ...rootParams + }); + + const mockVideoBid = (rootParams = {}, params = {}, videoParams = {}) => ({ + bidder: 'yieldmo', + adUnitCode: 'adunit-code-video', + bidId: '321video123', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream', + mimes: ['video/mp4'] + }, }, - }; - let bidArray = [bid]; - let bidderRequest = { + params: { + placementId: '123', + ...params, + video: { + placement: 1, + maxduration: 30, + startdelay: 10, + protocols: [2, 3], + api: [2, 3], + skipppable: true, + playbackmethod: [1, 2], + ...videoParams + } + }, + ...rootParams + }); + + const mockBidderRequest = (params = {}, bids = [mockBannerBid()]) => ({ bidderCode: 'yieldmo', auctionId: 'e3a336ad-2761-4a1c-b421-ecc7c5294a34', bidderRequestId: '14c4ede8c693f', - bids: bidArray, + bids, auctionStart: 1520001292880, timeout: 3000, start: 1520001292884, @@ -49,236 +75,215 @@ describe('YieldmoAdapter', function () { reachedTop: true, referer: 'yieldmo.com', }, - }; + ...params + }); describe('isBidRequestValid', function () { - it('should return true when necessary information is found', function () { - expect(spec.isBidRequestValid(bid)).to.be.true; + describe('Banner:', function () { + it('should return true when necessary information is found', function () { + expect(spec.isBidRequestValid(mockBannerBid())).to.be.true; + }); + + it('should return false when necessary information is not found', function () { + // empty bid + expect(spec.isBidRequestValid({})).to.be.false; + + // empty bidId + expect(spec.isBidRequestValid(mockBannerBid({bidId: ''}))).to.be.false; + + // empty adUnitCode + expect(spec.isBidRequestValid(mockBannerBid({adUnitCode: ''}))).to.be.false; + + let invalidBid = mockBannerBid(); + delete invalidBid.mediaTypes.banner; + expect(spec.isBidRequestValid(invalidBid)).to.be.false; + }); }); - it('should return false when necessary information is not found', function () { - // empty bid - expect(spec.isBidRequestValid({})).to.be.false; + describe('Instream video:', function () { + const getVideoBidWithoutParam = (key, paramToRemove) => { + let bid = mockVideoBid(); + delete utils.deepAccess(bid, key)[paramToRemove]; + return bid; + } + + it('should return true when necessary information is found', function () { + expect(spec.isBidRequestValid(mockVideoBid())).to.be.true; + }); + + it('should return false when necessary information is not found', function () { + // empty bidId + expect(spec.isBidRequestValid(mockVideoBid({bidId: ''}))).to.be.false; - // empty bidId - bid.bidId = ''; - expect(spec.isBidRequestValid(bid)).to.be.false; + // empty adUnitCode + expect(spec.isBidRequestValid(mockVideoBid({adUnitCode: ''}))).to.be.false; + }); + + it('should return false when required mediaTypes.video.* param is not found', function () { + const getBidAndExclude = paramToRemove => getVideoBidWithoutParam('mediaTypes.video', paramToRemove); + + expect(spec.isBidRequestValid(getBidAndExclude('playerSize'))).to.be.false; + expect(spec.isBidRequestValid(getBidAndExclude('mimes'))).to.be.false; + }); + + it('should return false when required bid.params.* is not found', function () { + const getBidAndExclude = paramToRemove => getVideoBidWithoutParam('params', paramToRemove); + + expect(spec.isBidRequestValid(getBidAndExclude('placementId'))).to.be.false; + expect(spec.isBidRequestValid(getBidAndExclude('video'))).to.be.false; + }); - // empty adUnitCode - bid.bidId = '30b31c1838de1e'; - bid.adUnitCode = ''; - expect(spec.isBidRequestValid(bid)).to.be.false; + it('should return false when required bid.params.video.* is not found', function () { + const getBidAndExclude = paramToRemove => getVideoBidWithoutParam('params.video', paramToRemove); - bid.adUnitCode = 'adunit-code'; + expect(spec.isBidRequestValid(getBidAndExclude('placement'))).to.be.false; + expect(spec.isBidRequestValid(getBidAndExclude('maxduration'))).to.be.false; + expect(spec.isBidRequestValid(getBidAndExclude('startdelay'))).to.be.false; + expect(spec.isBidRequestValid(getBidAndExclude('protocols'))).to.be.false; + expect(spec.isBidRequestValid(getBidAndExclude('api'))).to.be.false; + }); }); }); describe('buildRequests', function () { - it('should attempt to send bid requests to the endpoint via GET', function () { - const request = spec.buildRequests(bidArray, bidderRequest); - expect(request.method).to.equal('GET'); - expect(request.url).to.be.equal(ENDPOINT); - }); + const build = (bidRequests, bidderReq = mockBidderRequest()) => spec.buildRequests(bidRequests, bidderReq); + const buildAndGetPlacementInfo = (bidRequests, index = 0, bidderReq = mockBidderRequest()) => + utils.deepAccess(build(bidRequests, bidderReq), `${index}.data.p`); + const buildAndGetData = (bidRequests, index = 0, bidderReq = mockBidderRequest()) => + utils.deepAccess(build(bidRequests, bidderReq), `${index}.data`) || {}; - it('should not blow up if crumbs is undefined', function () { - let bidArray = [{ ...bid, crumbs: undefined }]; - expect(function () { - spec.buildRequests(bidArray, bidderRequest); - }).not.to.throw(); - }); + describe('Banner:', function () { + it('should attempt to send banner bid requests to the endpoint via GET', function () { + const requests = build([mockBannerBid()]); + expect(requests.length).to.equal(1); + expect(requests[0].method).to.equal('GET'); + expect(requests[0].url).to.be.equal(BANNER_ENDPOINT); + }); - it('should place bid information into the p parameter of data', function () { - let placementInfo = spec.buildRequests(bidArray, bidderRequest).data.p; - expect(placementInfo).to.equal( - '[{"placement_id":"adunit-code","callback_id":"30b31c1838de1e","sizes":[[300,250],[300,600]],"bidFloor":0.1}]' - ); - bidArray.push({ - bidder: 'yieldmo', - params: { - bidFloor: 0.2, - }, - adUnitCode: 'adunit-code-1', - mediaTypes: { - banner: { - sizes: [ - [300, 250], - [300, 600], - ], - }, - }, - bidId: '123456789', - bidderRequestId: '987654321', - auctionId: '0246810', - crumbs: { - pubcid: 'c604130c-0144-4b63-9bf2-c2bd8c8d86da', - }, + it('should not blow up if crumbs is undefined', function () { + expect(function () { + build([mockBannerBid({crumbs: undefined})]); + }).not.to.throw(); }); - // multiple placements - placementInfo = spec.buildRequests(bidArray, bidderRequest).data.p; - expect(placementInfo).to.equal( - '[{"placement_id":"adunit-code","callback_id":"30b31c1838de1e","sizes":[[300,250],[300,600]],"bidFloor":0.1},{"placement_id":"adunit-code-1","callback_id":"123456789","sizes":[[300,250],[300,600]],"bidFloor":0.2}]' - ); - }); + it('should place bid information into the p parameter of data', function () { + let bidArray = [mockBannerBid()]; + expect(buildAndGetPlacementInfo(bidArray)).to.equal( + '[{"placement_id":"adunit-code","callback_id":"30b31c1838de1e","sizes":[[300,250],[300,600]],"bidFloor":0.1}]' + ); - it('should add placement id if given', function () { - bidArray[0].params.placementId = 'ym_1293871298'; - let placementInfo = spec.buildRequests(bidArray, bidderRequest).data.p; - expect(placementInfo).to.include('"ym_placement_id":"ym_1293871298"'); - expect(placementInfo).not.to.include('"ym_placement_id":"ym_0987654321"'); + // multiple placements + bidArray.push(mockBannerBid( + {adUnitCode: 'adunit-2', bidId: '123a', bidderRequestId: '321', auctionId: '222'}, {bidFloor: 0.2})); + expect(buildAndGetPlacementInfo(bidArray)).to.equal( + '[{"placement_id":"adunit-code","callback_id":"30b31c1838de1e","sizes":[[300,250],[300,600]],"bidFloor":0.1},' + + '{"placement_id":"adunit-2","callback_id":"123a","sizes":[[300,250],[300,600]],"bidFloor":0.2}]' + ); + }); - bidArray[1].params.placementId = 'ym_0987654321'; - placementInfo = spec.buildRequests(bidArray, bidderRequest).data.p; - expect(placementInfo).to.include('"ym_placement_id":"ym_1293871298"'); - expect(placementInfo).to.include('"ym_placement_id":"ym_0987654321"'); - }); + it('should add placement id if given', function () { + let bidArray = [mockBannerBid({}, {placementId: 'ym_1293871298'})]; + let placementInfo = buildAndGetPlacementInfo(bidArray); + expect(placementInfo).to.include('"ym_placement_id":"ym_1293871298"'); + expect(placementInfo).not.to.include('"ym_placement_id":"ym_0987654321"'); - it('should add additional information to data parameter of request', function () { - const data = spec.buildRequests(bidArray, bidderRequest).data; - expect(data.hasOwnProperty('page_url')).to.be.true; - expect(data.hasOwnProperty('bust')).to.be.true; - expect(data.hasOwnProperty('pr')).to.be.true; - expect(data.hasOwnProperty('scrd')).to.be.true; - expect(data.dnt).to.be.false; - expect(data.hasOwnProperty('description')).to.be.true; - expect(data.hasOwnProperty('title')).to.be.true; - expect(data.hasOwnProperty('h')).to.be.true; - expect(data.hasOwnProperty('w')).to.be.true; - expect(data.hasOwnProperty('pubcid')).to.be.true; - expect(data.userConsent).to.equal('{"gdprApplies":"","cmp":""}'); - expect(data.us_privacy).to.equal(''); - }); + bidArray.push(mockBannerBid({}, {placementId: 'ym_0987654321'})); + placementInfo = buildAndGetPlacementInfo(bidArray); + expect(placementInfo).to.include('"ym_placement_id":"ym_1293871298"'); + expect(placementInfo).to.include('"ym_placement_id":"ym_0987654321"'); + }); - it('should add pubcid as parameter of request', function () { - const pubcidBid = { - bidder: 'yieldmo', - params: {}, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [ - [300, 250], - [300, 600], - ], - }, - }, - bidId: '30b31c1838de1e', - bidderRequestId: '22edbae2733bf6', - auctionId: '1d1a030790a475', - userId: { - pubcid: 'c604130c-0144-4b63-9bf2-c2bd8c8d86da2', - }, - }; - const data = spec.buildRequests([pubcidBid], bidderRequest).data; - expect(data.pubcid).to.deep.equal( - 'c604130c-0144-4b63-9bf2-c2bd8c8d86da2' - ); - }); + it('should add additional information to data parameter of request', function () { + const data = buildAndGetData([mockBannerBid()]); + expect(data.hasOwnProperty('page_url')).to.be.true; + expect(data.hasOwnProperty('bust')).to.be.true; + expect(data.hasOwnProperty('pr')).to.be.true; + expect(data.hasOwnProperty('scrd')).to.be.true; + expect(data.dnt).to.be.false; + expect(data.hasOwnProperty('description')).to.be.true; + expect(data.hasOwnProperty('title')).to.be.true; + expect(data.hasOwnProperty('h')).to.be.true; + expect(data.hasOwnProperty('w')).to.be.true; + expect(data.hasOwnProperty('pubcid')).to.be.true; + expect(data.userConsent).to.equal('{"gdprApplies":"","cmp":""}'); + expect(data.us_privacy).to.equal(''); + }); - it('should add unified id as parameter of request', function () { - const unifiedIdBid = { - bidder: 'yieldmo', - params: {}, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [ - [300, 250], - [300, 600], - ], - }, - }, - bidId: '30b31c1838de1e', - bidderRequestId: '22edbae2733bf6', - auctionId: '1d1a030790a475', - userId: { - tdid, - }, - }; - const data = spec.buildRequests([unifiedIdBid], bidderRequest).data; - expect(data.tdid).to.deep.equal(tdid); - }); + it('should add pubcid as parameter of request', function () { + const pubcid = 'c604130c-0144-4b63-9bf2-c2bd8c8d86da2'; + const pubcidBid = mockBannerBid({crumbs: undefined, userId: {pubcid}}); + expect(buildAndGetData([pubcidBid]).pubcid).to.deep.equal(pubcid); + }); - it('should add CRITEO RTUS id as parameter of request', function () { - const criteoIdBid = { - bidder: 'yieldmo', - params: {}, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [ - [300, 250], - [300, 600], - ], - }, - }, - bidId: '30b31c1838de1e', - bidderRequestId: '22edbae2733bf6', - auctionId: '1d1a030790a475', - userId: { - criteoId, - }, - }; - const data = spec.buildRequests([criteoIdBid], bidderRequest).data; - expect(data.cri_prebid).to.deep.equal(criteoId); - }); + it('should add unified id as parameter of request', function () { + const unifiedIdBid = mockBannerBid({crumbs: undefined}); + expect(buildAndGetData([unifiedIdBid]).tdid).to.deep.equal(mockBannerBid().userId.tdid); + }); + + it('should add CRITEO RTUS id as parameter of request', function () { + const criteoId = 'aff4'; + const criteoIdBid = mockBannerBid({crumbs: undefined, userId: { criteoId }}); + expect(buildAndGetData([criteoIdBid]).cri_prebid).to.deep.equal(criteoId); + }); - it('should add gdpr information to request if available', () => { - bidderRequest.gdprConsent = { - consentString: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', - vendorData: { blerp: 1 }, - gdprApplies: true, - }; - const data = spec.buildRequests(bidArray, bidderRequest).data; - expect(data.userConsent).equal( - JSON.stringify({ + it('should add gdpr information to request if available', () => { + const gdprConsent = { + consentString: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', + vendorData: {blerp: 1}, gdprApplies: true, - cmp: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', - }) - ); - }); + }; + const data = buildAndGetData([mockBannerBid()], 0, mockBidderRequest({gdprConsent})); + expect(data.userConsent).equal( + JSON.stringify({ + gdprApplies: true, + cmp: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', + }) + ); + }); + + it('should add ccpa information to request if available', () => { + const uspConsent = '1YNY'; + const data = buildAndGetData([mockBannerBid()], 0, mockBidderRequest({uspConsent})); + expect(data.us_privacy).equal(uspConsent); + }); - it('should add ccpa information to request if available', () => { - const privacy = '1YNY'; - bidderRequest.uspConsent = privacy; - const data = spec.buildRequests(bidArray, bidderRequest).data; - expect(data.us_privacy).equal(privacy); + it('should add schain if it is in the bidRequest', () => { + const schain = { + ver: '1.0', + complete: 1, + nodes: [{asi: 'indirectseller.com', sid: '00001', hp: 1}], + }; + const data = buildAndGetData([mockBannerBid({schain})]); + expect(data.schain).equal(JSON.stringify(schain)); + }); }); - it('should add schain if it is in the bidRequest', () => { - const schain = { - ver: '1.0', - complete: 1, - nodes: [{ asi: 'indirectseller.com', sid: '00001', hp: 1 }], - }; - bidArray[0].schain = schain; - const request = spec.buildRequests([bidArray[0]], bidderRequest); - expect(request.data.schain).equal(JSON.stringify(schain)); + describe('Instream video:', function () { + it('should attempt to send banner bid requests to the endpoint via POST', function () { + const requests = build([mockVideoBid()]); + expect(requests.length).to.equal(1); + expect(requests[0].method).to.equal('POST'); + expect(requests[0].url).to.be.equal(VIDEO_ENDPOINT); + }); }); }); describe('interpretResponse', function () { - let serverResponse; - - beforeEach(function () { - serverResponse = { - body: [ - { - callback_id: '21989fdbef550a', - cpm: 3.45455, - width: 300, - height: 250, - ad: - '
', - creative_id: '9874652394875', - }, - ], - header: 'header?', - }; + const mockServerResponse = () => ({ + body: [{ + callback_id: '21989fdbef550a', + cpm: 3.45455, + width: 300, + height: 250, + ad: '' + + '
', + creative_id: '9874652394875', + }], + header: 'header?', }); it('should correctly reorder the server response', function () { - const newResponse = spec.interpretResponse(serverResponse); + const newResponse = spec.interpretResponse(mockServerResponse()); expect(newResponse.length).to.be.equal(1); expect(newResponse[0]).to.deep.equal({ requestId: '21989fdbef550a', @@ -289,19 +294,18 @@ describe('YieldmoAdapter', function () { currency: 'USD', netRevenue: true, ttl: 300, - ad: - '
', + ad: '' + + '
', }); }); it('should not add responses if the cpm is 0 or null', function () { - serverResponse.body[0].cpm = 0; - let response = spec.interpretResponse(serverResponse); - expect(response).to.deep.equal([]); + let response = mockServerResponse(); + response.body[0].cpm = 0; + expect(spec.interpretResponse(response)).to.deep.equal([]); - serverResponse.body[0].cpm = null; - response = spec.interpretResponse(serverResponse); - expect(response).to.deep.equal([]); + response.body[0].cpm = null; + expect(spec.interpretResponse(response)).to.deep.equal([]); }); }); From d5a08ce227b94f79d98c25a0adf0c10e4436fc17 Mon Sep 17 00:00:00 2001 From: pnh-pubx <73683023+pnh-pubx@users.noreply.github.com> Date: Thu, 19 Nov 2020 14:03:00 +0530 Subject: [PATCH 031/103] PubxAi analytics adapter (#5915) * Added PubxAi analytics adapter * Updated Pubxai Analytics Adapter documentation * Updated test cases * Fixed issues with test cases * Updated deviceType and platform in the specs to fix browserstack errors * Updated documentation with description of each property * Updated hostname in the documentation Co-authored-by: Phaneendra Hegde --- modules/pubxaiAnalyticsAdapter.js | 168 ++++ modules/pubxaiAnalyticsAdapter.md | 27 + .../modules/pubxaiAnalyticsAdapter_spec.js | 734 ++++++++++++++++++ 3 files changed, 929 insertions(+) create mode 100644 modules/pubxaiAnalyticsAdapter.js create mode 100644 modules/pubxaiAnalyticsAdapter.md create mode 100644 test/spec/modules/pubxaiAnalyticsAdapter_spec.js diff --git a/modules/pubxaiAnalyticsAdapter.js b/modules/pubxaiAnalyticsAdapter.js new file mode 100644 index 00000000000..7e2f5061621 --- /dev/null +++ b/modules/pubxaiAnalyticsAdapter.js @@ -0,0 +1,168 @@ +import { ajax } from '../src/ajax.js'; +import adapter from '../src/AnalyticsAdapter.js'; +import adapterManager from '../src/adapterManager.js'; +import CONSTANTS from '../src/constants.json'; +import * as utils from '../src/utils.js'; + +const emptyUrl = ''; +const analyticsType = 'endpoint'; +const pubxaiAnalyticsVersion = 'v1.0.0'; +const defaultHost = 'api.pbxai.com'; +const auctionPath = '/analytics/auction'; +const winningBidPath = '/analytics/bidwon'; + +let initOptions; +let auctionTimestamp; +let events = { + bids: [] +}; + +var pubxaiAnalyticsAdapter = Object.assign(adapter( + { + emptyUrl, + analyticsType + }), { + track({ eventType, args }) { + if (typeof args !== 'undefined') { + if (eventType === CONSTANTS.EVENTS.BID_TIMEOUT) { + args.forEach(item => { mapBidResponse(item, 'timeout'); }); + } else if (eventType === CONSTANTS.EVENTS.AUCTION_INIT) { + events.auctionInit = args; + auctionTimestamp = args.timestamp; + } else if (eventType === CONSTANTS.EVENTS.BID_REQUESTED) { + mapBidRequests(args).forEach(item => { events.bids.push(item) }); + } else if (eventType === CONSTANTS.EVENTS.BID_RESPONSE) { + mapBidResponse(args, 'response'); + } else if (eventType === CONSTANTS.EVENTS.BID_WON) { + send({ + winningBid: mapBidResponse(args, 'bidwon') + }, 'bidwon'); + } + } + + if (eventType === CONSTANTS.EVENTS.AUCTION_END) { + send(events, 'auctionEnd'); + } + } +}); + +function mapBidRequests(params) { + let arr = []; + if (typeof params.bids !== 'undefined' && params.bids.length) { + params.bids.forEach(function (bid) { + arr.push({ + bidderCode: bid.bidder, + bidId: bid.bidId, + adUnitCode: bid.adUnitCode, + requestId: bid.bidderRequestId, + auctionId: bid.auctionId, + transactionId: bid.transactionId, + sizes: utils.parseSizesInput(bid.mediaTypes.banner.sizes).toString(), + renderStatus: 1, + requestTimestamp: params.auctionStart + }); + }); + } + return arr; +} + +function mapBidResponse(bidResponse, status) { + if (status !== 'bidwon') { + let bid = events.bids.filter(o => o.bidId === bidResponse.bidId || o.bidId === bidResponse.requestId)[0]; + Object.assign(bid, { + bidderCode: bidResponse.bidder, + bidId: status === 'timeout' ? bidResponse.bidId : bidResponse.requestId, + adUnitCode: bidResponse.adUnitCode, + auctionId: bidResponse.auctionId, + creativeId: bidResponse.creativeId, + transactionId: bidResponse.transactionId, + currency: bidResponse.currency, + cpm: bidResponse.cpm, + netRevenue: bidResponse.netRevenue, + mediaType: bidResponse.mediaType, + statusMessage: bidResponse.statusMessage, + floorData: bidResponse.floorData, + status: bidResponse.status, + renderStatus: status === 'timeout' ? 3 : 2, + timeToRespond: bidResponse.timeToRespond, + requestTimestamp: bidResponse.requestTimestamp, + responseTimestamp: bidResponse.responseTimestamp, + platform: navigator.platform, + deviceType: getDeviceType() + }); + } else { + return { + bidderCode: bidResponse.bidder, + bidId: bidResponse.requestId, + adUnitCode: bidResponse.adUnitCode, + auctionId: bidResponse.auctionId, + creativeId: bidResponse.creativeId, + transactionId: bidResponse.transactionId, + currency: bidResponse.currency, + cpm: bidResponse.cpm, + netRevenue: bidResponse.netRevenue, + floorData: bidResponse.floorData, + renderedSize: bidResponse.size, + mediaType: bidResponse.mediaType, + statusMessage: bidResponse.statusMessage, + status: bidResponse.status, + renderStatus: 4, + timeToRespond: bidResponse.timeToRespond, + requestTimestamp: bidResponse.requestTimestamp, + responseTimestamp: bidResponse.responseTimestamp, + platform: navigator.platform, + deviceType: getDeviceType() + } + } +} + +export function getDeviceType() { + if ((/ipad|android 3.0|xoom|sch-i800|playbook|tablet|kindle/i.test(navigator.userAgent.toLowerCase()))) { + return 'tablet'; + } + if ((/iphone|ipod|android|blackberry|opera|mini|windows\sce|palm|smartphone|iemobile/i.test(navigator.userAgent.toLowerCase()))) { + return 'mobile'; + } + return 'desktop'; +} + +// add sampling rate +pubxaiAnalyticsAdapter.shouldFireEventRequest = function (samplingRate = 1) { + return (Math.floor((Math.random() * samplingRate + 1)) === parseInt(samplingRate)); +} + +function send(data, status) { + if (pubxaiAnalyticsAdapter.shouldFireEventRequest(initOptions.samplingRate)) { + let location = utils.getWindowLocation(); + if (typeof data !== 'undefined' && typeof data.auctionInit !== 'undefined') { + Object.assign(data.auctionInit, { host: location.host, path: location.pathname, search: location.search }); + } + data.initOptions = initOptions; + + let pubxaiAnalyticsRequestUrl = utils.buildUrl({ + protocol: 'https', + hostname: (initOptions && initOptions.hostName) || defaultHost, + pathname: status == 'bidwon' ? winningBidPath : auctionPath, + search: { + auctionTimestamp: auctionTimestamp, + pubxaiAnalyticsVersion: pubxaiAnalyticsVersion, + prebidVersion: $$PREBID_GLOBAL$$.version + } + }); + + ajax(pubxaiAnalyticsRequestUrl, undefined, JSON.stringify(data), { method: 'POST', contentType: 'text/plain' }); + } +} + +pubxaiAnalyticsAdapter.originEnableAnalytics = pubxaiAnalyticsAdapter.enableAnalytics; +pubxaiAnalyticsAdapter.enableAnalytics = function (config) { + initOptions = config.options; + pubxaiAnalyticsAdapter.originEnableAnalytics(config); +}; + +adapterManager.registerAnalyticsAdapter({ + adapter: pubxaiAnalyticsAdapter, + code: 'pubxai' +}); + +export default pubxaiAnalyticsAdapter; diff --git a/modules/pubxaiAnalyticsAdapter.md b/modules/pubxaiAnalyticsAdapter.md new file mode 100644 index 00000000000..112329fc171 --- /dev/null +++ b/modules/pubxaiAnalyticsAdapter.md @@ -0,0 +1,27 @@ +# Overview +Module Name: PubX.io Analytics Adapter +Module Type: Analytics Adapter +Maintainer: phaneendra@pubx.ai + +# Description + +Analytics adapter for prebid provided by Pubx.ai. Contact alex@pubx.ai for information. + +# Test Parameters + +``` +{ + provider: 'pubxai', + options : { + pubxId: 'xxx', + hostName: 'example.com', + samplingRate: 1 + } +} +``` +Property | Data Type | Is required? | Description |Example +:-----:|:-----:|:-----:|:-----:|:-----: +pubxId|string|Yes | A unique identifier provided by PubX.ai to indetify publishers. |`"a9d48e2f-24ec-4ec1-b3e2-04e32c3aeb03"` +hostName|string|No|hostName is provided by Pubx.ai. |`"https://example.com"` +samplingRate |number |No|How often the sampling must be taken. |`2` - (sample one in two cases) \ `3` - (sample one in three cases) + | | | | \ No newline at end of file diff --git a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..91c81dcae8d --- /dev/null +++ b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js @@ -0,0 +1,734 @@ +import pubxaiAnalyticsAdapter from 'modules/pubxaiAnalyticsAdapter.js'; +import { getDeviceType } from 'modules/pubxaiAnalyticsAdapter.js'; +import { + expect +} from 'chai'; +import adapterManager from 'src/adapterManager.js'; +import * as utils from 'src/utils.js'; +import { + server +} from 'test/mocks/xhr.js'; + +let events = require('src/events'); +let constants = require('src/constants.json'); + +describe('pubxai analytics adapter', function() { + beforeEach(function() { + sinon.stub(events, 'getEvents').returns([]); + }); + + afterEach(function() { + events.getEvents.restore(); + }); + + describe('track', function() { + let initOptions = { + samplingRate: '1', + pubxId: '6c415fc0-8b0e-4cf5-be73-01526a4db625' + }; + + let prebidEvent = { + 'auctionInit': { + 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', + 'timestamp': 1603865707180, + 'auctionStatus': 'inProgress', + 'adUnits': [{ + 'code': '/19968336/header-bid-tag-1', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ] + ] + } + }, + 'bids': [{ + 'bidder': 'appnexus', + 'params': { + 'placementId': 13144370 + }, + 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', + 'floorData': { + 'skipped': false, + 'skipRate': 0, + 'modelVersion': 'new model 1.0', + 'location': 'fetch', + 'floorProvider': 'PubXFloor', + 'fetchStatus': 'success' + } + }], + 'sizes': [ + [ + 300, + 250 + ] + ], + 'transactionId': '41ec8eaf-3e7c-4a8b-8344-ab796ff6e294' + }], + 'adUnitCodes': [ + '/19968336/header-bid-tag-1' + ], + 'bidderRequests': [{ + 'bidderCode': 'appnexus', + 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', + 'bidderRequestId': '184cbc05bb90ba', + 'bids': [{ + 'bidder': 'appnexus', + 'params': { + 'placementId': 13144370 + }, + 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', + 'floorData': { + 'skipped': false, + 'skipRate': 0, + 'modelVersion': 'new model 1.0', + 'location': 'fetch', + 'floorProvider': 'PubXFloor', + 'fetchStatus': 'success' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ] + ] + } + }, + 'adUnitCode': '/19968336/header-bid-tag-1', + 'transactionId': '41ec8eaf-3e7c-4a8b-8344-ab796ff6e294', + 'sizes': [ + [ + 300, + 250 + ] + ], + 'bidId': '248f9a4489835e', + 'bidderRequestId': '184cbc05bb90ba', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + }], + 'auctionStart': 1603865707180, + 'timeout': 1000, + 'refererInfo': { + 'referer': 'http://local-pnh.net:8080/stream/', + 'reachedTop': true, + 'isAmp': false, + 'numIframes': 0, + 'stack': [ + 'http://local-pnh.net:8080/stream/' + ], + 'canonicalUrl': null + }, + 'start': 1603865707182 + }], + 'noBids': [], + 'bidsReceived': [], + 'winningBids': [], + 'timeout': 1000, + 'config': { + 'samplingRate': '1', + 'pubxId': '6c415fc0-8b0e-4cf5-be73-01526a4db625' + } + }, + 'bidRequested': { + 'bidderCode': 'appnexus', + 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', + 'bidderRequestId': '184cbc05bb90ba', + 'bids': [{ + 'bidder': 'appnexus', + 'params': { + 'placementId': 13144370 + }, + 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', + 'floorData': { + 'skipped': false, + 'skipRate': 0, + 'modelVersion': 'new model 1.0', + 'location': 'fetch', + 'floorProvider': 'PubXFloor', + 'fetchStatus': 'success' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ] + ] + } + }, + 'adUnitCode': '/19968336/header-bid-tag-1', + 'transactionId': '41ec8eaf-3e7c-4a8b-8344-ab796ff6e294', + 'sizes': [ + [ + 300, + 250 + ] + ], + 'bidId': '248f9a4489835e', + 'bidderRequestId': '184cbc05bb90ba', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + }], + 'auctionStart': 1603865707180, + 'timeout': 1000, + 'refererInfo': { + 'referer': 'http://local-pnh.net:8080/stream/', + 'reachedTop': true, + 'isAmp': false, + 'numIframes': 0, + 'stack': [ + 'http://local-pnh.net:8080/stream/' + ], + 'canonicalUrl': null + }, + 'start': 1603865707182 + }, + 'bidTimeout': [], + 'bidResponse': { + 'bidderCode': 'appnexus', + 'width': 300, + 'height': 250, + 'statusMessage': 'Bid available', + 'adId': '32780c4bc382cb', + 'requestId': '248f9a4489835e', + 'mediaType': 'banner', + 'source': 'client', + 'cpm': 0.5, + 'creativeId': 96846035, + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 300, + 'adUnitCode': '/19968336/header-bid-tag-1', + 'appnexus': { + 'buyerMemberId': 9325 + }, + 'meta': { + 'advertiserId': 2529885 + }, + 'ad': '', + 'originalCpm': 0.5, + 'originalCurrency': 'USD', + 'floorData': { + 'floorValue': 0.4, + 'floorRule': '/19968336/header-bid-tag-1|banner', + 'floorCurrency': 'USD', + 'cpmAfterAdjustments': 0.5, + 'enforcements': { + 'enforceJS': true, + 'enforcePBS': false, + 'floorDeals': true, + 'bidAdjustment': true + }, + 'matchedFields': { + 'gptSlot': '/19968336/header-bid-tag-1', + 'mediaType': 'banner' + } + }, + 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', + 'responseTimestamp': 1603865707449, + 'requestTimestamp': 1603865707182, + 'bidder': 'appnexus', + 'timeToRespond': 267, + 'pbLg': '0.50', + 'pbMg': '0.50', + 'pbHg': '0.50', + 'pbAg': '0.50', + 'pbDg': '0.50', + 'pbCg': '0.50', + 'size': '300x250', + 'adserverTargeting': { + 'hb_bidder': 'appnexus', + 'hb_adid': '32780c4bc382cb', + 'hb_pb': '0.50', + 'hb_size': '300x250', + 'hb_source': 'client', + 'hb_format': 'banner' + }, + }, + 'auctionEnd': { + 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', + 'timestamp': 1603865707180, + 'auctionEnd': 1603865707180, + 'auctionStatus': 'completed', + 'adUnits': [{ + 'code': '/19968336/header-bid-tag-1', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ] + ] + } + }, + 'bids': [{ + 'bidder': 'appnexus', + 'params': { + 'placementId': 13144370 + }, + 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', + 'floorData': { + 'skipped': false, + 'skipRate': 0, + 'modelVersion': 'new model 1.0', + 'location': 'fetch', + 'floorProvider': 'PubXFloor', + 'fetchStatus': 'success' + } + }], + 'sizes': [ + [ + 300, + 250 + ] + ], + 'transactionId': '41ec8eaf-3e7c-4a8b-8344-ab796ff6e294' + }], + 'adUnitCodes': [ + '/19968336/header-bid-tag-1' + ], + 'bidderRequests': [{ + 'bidderCode': 'appnexus', + 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', + 'bidderRequestId': '184cbc05bb90ba', + 'bids': [{ + 'bidder': 'appnexus', + 'params': { + 'placementId': 13144370 + }, + 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', + 'floorData': { + 'skipped': false, + 'skipRate': 0, + 'modelVersion': 'new model 1.0', + 'location': 'fetch', + 'floorProvider': 'PubXFloor', + 'fetchStatus': 'success' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ] + ] + } + }, + 'adUnitCode': '/19968336/header-bid-tag-1', + 'transactionId': '41ec8eaf-3e7c-4a8b-8344-ab796ff6e294', + 'sizes': [ + [ + 300, + 250 + ] + ], + 'bidId': '248f9a4489835e', + 'bidderRequestId': '184cbc05bb90ba', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + }], + 'auctionStart': 1603865707180, + 'timeout': 1000, + 'refererInfo': { + 'referer': 'http://local-pnh.net:8080/stream/', + 'reachedTop': true, + 'isAmp': false, + 'numIframes': 0, + 'stack': [ + 'http://local-pnh.net:8080/stream/' + ], + 'canonicalUrl': null + }, + 'start': 1603865707182 + }], + 'noBids': [], + 'bidsReceived': [{ + 'bidderCode': 'appnexus', + 'width': 300, + 'height': 250, + 'statusMessage': 'Bid available', + 'adId': '32780c4bc382cb', + 'requestId': '248f9a4489835e', + 'mediaType': 'banner', + 'source': 'client', + 'cpm': 0.5, + 'creativeId': 96846035, + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 300, + 'adUnitCode': '/19968336/header-bid-tag-1', + 'appnexus': { + 'buyerMemberId': 9325 + }, + 'meta': { + 'advertiserId': 2529885 + }, + 'ad': '', + 'originalCpm': 0.5, + 'originalCurrency': 'USD', + 'floorData': { + 'floorValue': 0.4, + 'floorRule': '/19968336/header-bid-tag-1|banner', + 'floorCurrency': 'USD', + 'cpmAfterAdjustments': 0.5, + 'enforcements': { + 'enforceJS': true, + 'enforcePBS': false, + 'floorDeals': true, + 'bidAdjustment': true + }, + 'matchedFields': { + 'gptSlot': '/19968336/header-bid-tag-1', + 'mediaType': 'banner' + } + }, + 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', + 'responseTimestamp': 1603865707449, + 'requestTimestamp': 1603865707182, + 'bidder': 'appnexus', + 'timeToRespond': 267, + 'pbLg': '0.50', + 'pbMg': '0.50', + 'pbHg': '0.50', + 'pbAg': '0.50', + 'pbDg': '0.50', + 'pbCg': '0.50', + 'size': '300x250', + 'adserverTargeting': { + 'hb_bidder': 'appnexus', + 'hb_adid': '32780c4bc382cb', + 'hb_pb': '0.50', + 'hb_size': '300x250', + 'hb_source': 'client', + 'hb_format': 'banner' + }, + 'status': 'rendered', + 'params': [{ + 'placementId': 13144370 + }] + }], + 'winningBids': [], + 'timeout': 1000 + }, + 'bidWon': { + 'bidderCode': 'appnexus', + 'width': 300, + 'height': 250, + 'statusMessage': 'Bid available', + 'adId': '32780c4bc382cb', + 'requestId': '248f9a4489835e', + 'mediaType': 'banner', + 'source': 'client', + 'cpm': 0.5, + 'creativeId': 96846035, + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 300, + 'adUnitCode': '/19968336/header-bid-tag-1', + 'appnexus': { + 'buyerMemberId': 9325 + }, + 'meta': { + 'advertiserId': 2529885 + }, + 'ad': '', + 'originalCpm': 0.5, + 'originalCurrency': 'USD', + 'floorData': { + 'floorValue': 0.4, + 'floorRule': '/19968336/header-bid-tag-1|banner', + 'floorCurrency': 'USD', + 'cpmAfterAdjustments': 0.5, + 'enforcements': { + 'enforceJS': true, + 'enforcePBS': false, + 'floorDeals': true, + 'bidAdjustment': true + }, + 'matchedFields': { + 'gptSlot': '/19968336/header-bid-tag-1', + 'mediaType': 'banner' + } + }, + 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', + 'responseTimestamp': 1603865707449, + 'requestTimestamp': 1603865707182, + 'bidder': 'appnexus', + 'timeToRespond': 267, + 'pbLg': '0.50', + 'pbMg': '0.50', + 'pbHg': '0.50', + 'pbAg': '0.50', + 'pbDg': '0.50', + 'pbCg': '0.50', + 'size': '300x250', + 'adserverTargeting': { + 'hb_bidder': 'appnexus', + 'hb_adid': '32780c4bc382cb', + 'hb_pb': '0.50', + 'hb_size': '300x250', + 'hb_source': 'client', + 'hb_format': 'banner' + }, + 'status': 'rendered', + 'params': [{ + 'placementId': 13144370 + }] + } + }; + let location = utils.getWindowLocation(); + + let expectedAfterBid = { + 'bids': [{ + 'bidderCode': 'appnexus', + 'bidId': '248f9a4489835e', + 'adUnitCode': '/19968336/header-bid-tag-1', + 'requestId': '184cbc05bb90ba', + 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', + 'sizes': '300x250', + 'renderStatus': 2, + 'requestTimestamp': 1603865707182, + 'creativeId': 96846035, + 'currency': 'USD', + 'cpm': 0.5, + 'netRevenue': true, + 'mediaType': 'banner', + 'statusMessage': 'Bid available', + 'floorData': { + 'floorValue': 0.4, + 'floorRule': '/19968336/header-bid-tag-1|banner', + 'floorCurrency': 'USD', + 'cpmAfterAdjustments': 0.5, + 'enforcements': { + 'enforceJS': true, + 'enforcePBS': false, + 'floorDeals': true, + 'bidAdjustment': true + }, + 'matchedFields': { + 'gptSlot': '/19968336/header-bid-tag-1', + 'mediaType': 'banner' + } + }, + 'timeToRespond': 267, + 'responseTimestamp': 1603865707449, + 'platform': navigator.platform, + 'deviceType': getDeviceType() + }], + 'auctionInit': { + 'host': location.host, + 'path': location.pathname, + 'search': location.search, + 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', + 'timestamp': 1603865707180, + 'auctionStatus': 'inProgress', + 'adUnits': [{ + 'code': '/19968336/header-bid-tag-1', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ] + ] + } + }, + 'bids': [{ + 'bidder': 'appnexus', + 'params': { + 'placementId': 13144370 + }, + 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', + 'floorData': { + 'skipped': false, + 'skipRate': 0, + 'modelVersion': 'new model 1.0', + 'location': 'fetch', + 'floorProvider': 'PubXFloor', + 'fetchStatus': 'success' + } + }], + 'sizes': [ + [ + 300, + 250 + ] + ], + 'transactionId': '41ec8eaf-3e7c-4a8b-8344-ab796ff6e294' + }], + 'adUnitCodes': [ + '/19968336/header-bid-tag-1' + ], + 'bidderRequests': [{ + 'bidderCode': 'appnexus', + 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', + 'bidderRequestId': '184cbc05bb90ba', + 'bids': [{ + 'bidder': 'appnexus', + 'params': { + 'placementId': 13144370 + }, + 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', + 'floorData': { + 'skipped': false, + 'skipRate': 0, + 'modelVersion': 'new model 1.0', + 'location': 'fetch', + 'floorProvider': 'PubXFloor', + 'fetchStatus': 'success' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ] + ] + } + }, + 'adUnitCode': '/19968336/header-bid-tag-1', + 'transactionId': '41ec8eaf-3e7c-4a8b-8344-ab796ff6e294', + 'sizes': [ + [ + 300, + 250 + ] + ], + 'bidId': '248f9a4489835e', + 'bidderRequestId': '184cbc05bb90ba', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + }], + 'auctionStart': 1603865707180, + 'timeout': 1000, + 'refererInfo': { + 'referer': 'http://local-pnh.net:8080/stream/', + 'reachedTop': true, + 'isAmp': false, + 'numIframes': 0, + 'stack': [ + 'http://local-pnh.net:8080/stream/' + ], + 'canonicalUrl': null + }, + 'start': 1603865707182 + }], + 'noBids': [], + 'bidsReceived': [], + 'winningBids': [], + 'timeout': 1000, + 'config': { + 'samplingRate': '1', + 'pubxId': '6c415fc0-8b0e-4cf5-be73-01526a4db625' + } + }, + 'initOptions': initOptions + }; + + let expectedAfterBidWon = { + 'winningBid': { + 'bidderCode': 'appnexus', + 'bidId': '248f9a4489835e', + 'adUnitCode': '/19968336/header-bid-tag-1', + 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', + 'renderedSize': '300x250', + 'renderStatus': 4, + 'requestTimestamp': 1603865707182, + 'creativeId': 96846035, + 'currency': 'USD', + 'cpm': 0.5, + 'netRevenue': true, + 'mediaType': 'banner', + 'status': 'rendered', + 'statusMessage': 'Bid available', + 'floorData': { + 'floorValue': 0.4, + 'floorRule': '/19968336/header-bid-tag-1|banner', + 'floorCurrency': 'USD', + 'cpmAfterAdjustments': 0.5, + 'enforcements': { + 'enforceJS': true, + 'enforcePBS': false, + 'floorDeals': true, + 'bidAdjustment': true + }, + 'matchedFields': { + 'gptSlot': '/19968336/header-bid-tag-1', + 'mediaType': 'banner' + } + }, + 'timeToRespond': 267, + 'responseTimestamp': 1603865707449, + 'platform': navigator.platform, + 'deviceType': getDeviceType() + }, + 'initOptions': initOptions + } + + adapterManager.registerAnalyticsAdapter({ + code: 'pubxai', + adapter: pubxaiAnalyticsAdapter + }); + + beforeEach(function() { + adapterManager.enableAnalytics({ + provider: 'pubxai', + options: initOptions + }); + }); + + afterEach(function() { + pubxaiAnalyticsAdapter.disableAnalytics(); + }); + + it('builds and sends auction data', function() { + // Step 1: Send auction init event + events.emit(constants.EVENTS.AUCTION_INIT, prebidEvent['auctionInit']); + + // Step 2: Send bid requested event + events.emit(constants.EVENTS.BID_REQUESTED, prebidEvent['bidRequested']); + + // Step 3: Send bid response event + events.emit(constants.EVENTS.BID_RESPONSE, prebidEvent['bidResponse']); + + // Step 4: Send bid time out event + events.emit(constants.EVENTS.BID_TIMEOUT, prebidEvent['bidTimeout']); + + // Step 5: Send auction end event + events.emit(constants.EVENTS.AUCTION_END, prebidEvent['auctionEnd']); + + expect(server.requests.length).to.equal(1); + + let realAfterBid = JSON.parse(server.requests[0].requestBody); + + expect(realAfterBid).to.deep.equal(expectedAfterBid); + + // Step 6: Send auction bid won event + events.emit(constants.EVENTS.BID_WON, prebidEvent['bidWon']); + + expect(server.requests.length).to.equal(2); + + let winEventData = JSON.parse(server.requests[1].requestBody); + + expect(winEventData).to.deep.equal(expectedAfterBidWon); + }); + }); +}); From 6fea8444b35b1c903c662603947f884059f92bc0 Mon Sep 17 00:00:00 2001 From: BizzClick <73241175+BizzClick@users.noreply.github.com> Date: Thu, 19 Nov 2020 10:37:09 +0200 Subject: [PATCH 032/103] init bizzclick prebid.js adapter (#5914) * init bizzclick prebid.js adapter * increase test coverage * init bizzclick prebid.js adapter * increase test coverage * fix linting --- modules/bizzclickBidAdapter.js | 307 +++++++++++++++ modules/bizzclickBidAdapter.md | 83 +++- test/spec/modules/bizzclickBidAdapter_spec.js | 369 ++++++++++++++++++ 3 files changed, 756 insertions(+), 3 deletions(-) create mode 100644 modules/bizzclickBidAdapter.js create mode 100644 test/spec/modules/bizzclickBidAdapter_spec.js diff --git a/modules/bizzclickBidAdapter.js b/modules/bizzclickBidAdapter.js new file mode 100644 index 00000000000..80d2f6b5395 --- /dev/null +++ b/modules/bizzclickBidAdapter.js @@ -0,0 +1,307 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import * as utils from '../src/utils.js'; +import {config} from '../src/config.js'; + +const BIDDER_CODE = 'bizzclick'; +const ACCOUNTID_MACROS = '[account_id]'; +const URL_ENDPOINT = `https://us-e-node1.bizzclick.com/bid?rtb_seat_id=prebidjs&secret_key=${ACCOUNTID_MACROS}`; +const NATIVE_ASSET_IDS = { 0: 'title', 2: 'icon', 3: 'image', 5: 'sponsoredBy', 4: 'body', 1: 'cta' }; +const NATIVE_PARAMS = { + title: { + id: 0, + name: 'title' + }, + icon: { + id: 2, + type: 1, + name: 'img' + }, + image: { + id: 3, + type: 3, + name: 'img' + }, + sponsoredBy: { + id: 5, + name: 'data', + type: 1 + }, + body: { + id: 4, + name: 'data', + type: 2 + }, + cta: { + id: 1, + type: 12, + name: 'data' + } +}; +const NATIVE_VERSION = '1.2'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + /** + * Determines whether or not the given bid request is valid. + * + * @param {object} bid The bid to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: (bid) => { + return Boolean(bid.params.accountId) && Boolean(bid.params.placementId) + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} validBidRequests A non-empty list of valid bid requests that should be sent to the Server. + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: (validBidRequests, bidderRequest) => { + if (validBidRequests && validBidRequests.length === 0) return [] + let accuontId = validBidRequests[0].params.accountId; + const endpointURL = URL_ENDPOINT.replace(ACCOUNTID_MACROS, accuontId); + + let winTop = window; + let location; + try { + location = new URL(bidderRequest.refererInfo.referer) + winTop = window.top; + } catch (e) { + location = winTop.location; + utils.logMessage(e); + }; + + let bids = []; + for (let bidRequest of validBidRequests) { + let impObject = prepareImpObject(bidRequest); + let data = { + id: bidRequest.bidId, + test: config.getConfig('debug') ? 1 : 0, + cur: ['USD'], + device: { + w: winTop.screen.width, + h: winTop.screen.height, + language: (navigator && navigator.language) ? navigator.language.indexOf('-') != -1 ? navigator.language.split('-')[0] : navigator.language : '', + }, + site: { + page: location.pathname, + host: location.host + }, + source: { + tid: bidRequest.transactionId + }, + tmax: bidRequest.timeout, + imp: [impObject], + }; + bids.push(data) + } + return { + method: 'POST', + url: endpointURL, + data: bids + }; + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {*} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: (serverResponse) => { + if (!serverResponse || !serverResponse.body) return [] + let bizzclickResponse = serverResponse.body; + + let bids = []; + for (let response of bizzclickResponse) { + let mediaType = response.seatbid[0].bid[0].ext && response.seatbid[0].bid[0].ext.mediaType ? response.seatbid[0].bid[0].ext.mediaType : BANNER; + + let bid = { + requestId: response.id, + cpm: response.seatbid[0].bid[0].price, + width: response.seatbid[0].bid[0].w, + height: response.seatbid[0].bid[0].h, + ttl: response.ttl || 1200, + currency: response.cur || 'USD', + netRevenue: true, + creativeId: response.seatbid[0].bid[0].crid, + dealId: response.seatbid[0].bid[0].dealid, + mediaType: mediaType + }; + + switch (mediaType) { + case VIDEO: + bid.vastXml = response.seatbid[0].bid[0].adm + bid.vastUrl = response.seatbid[0].bid[0].ext.vastUrl + break + case NATIVE: + bid.native = parseNative(response.seatbid[0].bid[0].adm) + break + default: + bid.ad = response.seatbid[0].bid[0].adm + } + + bids.push(bid); + } + + return bids; + }, +}; + +/** + * Determine type of request + * + * @param bidRequest + * @param type + * @returns {boolean} + */ +const checkRequestType = (bidRequest, type) => { + return (typeof utils.deepAccess(bidRequest, `mediaTypes.${type}`) !== 'undefined'); +} + +const parseNative = admObject => { + const { assets, link, imptrackers, jstracker } = admObject.native; + const result = { + clickUrl: link.url, + clickTrackers: link.clicktrackers || undefined, + impressionTrackers: imptrackers || undefined, + javascriptTrackers: jstracker ? [ jstracker ] : undefined + }; + assets.forEach(asset => { + const kind = NATIVE_ASSET_IDS[asset.id]; + const content = kind && asset[NATIVE_PARAMS[kind].name]; + if (content) { + result[kind] = content.text || content.value || { url: content.url, width: content.w, height: content.h }; + } + }); + + return result; +} + +const prepareImpObject = (bidRequest) => { + let impObject = { + id: bidRequest.transactionId, + secure: 1, + ext: { + placementId: bidRequest.params.placementId + } + }; + if (checkRequestType(bidRequest, BANNER)) { + impObject.banner = addBannerParameters(bidRequest); + } + if (checkRequestType(bidRequest, VIDEO)) { + impObject.video = addVideoParameters(bidRequest); + } + if (checkRequestType(bidRequest, NATIVE)) { + impObject.native = { + ver: NATIVE_VERSION, + request: addNativeParameters(bidRequest) + }; + } + return impObject +}; + +const addNativeParameters = bidRequest => { + let impObject = { + id: bidRequest.transactionId, + ver: NATIVE_VERSION, + }; + + const assets = utils._map(bidRequest.mediaTypes.native, (bidParams, key) => { + const props = NATIVE_PARAMS[key]; + const asset = { + required: bidParams.required & 1, + }; + if (props) { + asset.id = props.id; + let wmin, hmin; + let aRatios = bidParams.aspect_ratios; + + if (aRatios && aRatios[0]) { + aRatios = aRatios[0]; + wmin = aRatios.min_width || 0; + hmin = aRatios.ratio_height * wmin / aRatios.ratio_width | 0; + } + + if (bidParams.sizes) { + const sizes = flatten(bidParams.sizes); + wmin = sizes[0]; + hmin = sizes[1]; + } + + asset[props.name] = {} + + if (bidParams.len) asset[props.name]['len'] = bidParams.len; + if (props.type) asset[props.name]['type'] = props.type; + if (wmin) asset[props.name]['wmin'] = wmin; + if (hmin) asset[props.name]['hmin'] = hmin; + + return asset; + } + }).filter(Boolean); + + impObject.assets = assets; + return impObject +} + +const addBannerParameters = (bidRequest) => { + let bannerObject = {}; + const size = parseSizes(bidRequest, 'banner'); + bannerObject.w = size[0]; + bannerObject.h = size[1]; + return bannerObject; +}; + +const parseSizes = (bid, mediaType) => { + let mediaTypes = bid.mediaTypes; + if (mediaType === 'video') { + let size = []; + if (mediaTypes.video && mediaTypes.video.w && mediaTypes.video.h) { + size = [ + mediaTypes.video.w, + mediaTypes.video.h + ]; + } else if (Array.isArray(utils.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; + } + let sizes = []; + if (Array.isArray(mediaTypes.banner.sizes)) { + sizes = mediaTypes.banner.sizes[0]; + } else if (Array.isArray(bid.sizes) && bid.sizes.length > 0) { + sizes = bid.sizes + } else { + utils.logWarn('no sizes are setup or found'); + } + + return sizes +} + +const addVideoParameters = (bidRequest) => { + let videoObj = {}; + let supportParamsList = ['mimes', 'minduration', 'maxduration', 'protocols', 'startdelay', 'placement', 'skip', 'skipafter', 'minbitrate', 'maxbitrate', 'delivery', 'playbackmethod', 'api', 'linearity'] + + for (let param of supportParamsList) { + if (bidRequest.mediaTypes.video[param] !== undefined) { + videoObj[param] = bidRequest.mediaTypes.video[param]; + } + } + + const size = parseSizes(bidRequest, 'video'); + videoObj.w = size[0]; + videoObj.h = size[1]; + return videoObj; +} + +const flatten = arr => { + return [].concat(...arr); +} + +registerBidder(spec); diff --git a/modules/bizzclickBidAdapter.md b/modules/bizzclickBidAdapter.md index 7dfa458b34c..6fc1bebf546 100644 --- a/modules/bizzclickBidAdapter.md +++ b/modules/bizzclickBidAdapter.md @@ -14,14 +14,91 @@ Module that connects to BizzClick SSP demand sources ``` var adUnits = [{ code: 'placementId', - sizes: [[300, 250]], + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]] + } + }, bids: [{ bidder: 'bizzclick', params: { - placementId: 0, - type: 'banner' + placementId: 'hash', + accountId: 'accountId' } }] + }, + { + code: 'native_example', + // sizes: [[1, 1]], + mediaTypes: { + native: { + title: { + required: true, + len: 800 + }, + image: { + required: true, + len: 80 + }, + sponsoredBy: { + required: true + }, + clickUrl: { + required: true + }, + privacyLink: { + required: false + }, + body: { + required: true + }, + icon: { + required: true, + sizes: [50, 50] + } + } + + }, + bids: [ { + bidder: 'bizzclick', + params: { + placementId: 'hash', + accountId: 'accountId' + } + }] + }, + { + code: 'video1', + sizes: [640,480], + mediaTypes: { video: { + minduration:0, + maxduration:999, + boxingallowed:1, + skip:0, + mimes:[ + 'application/javascript', + 'video/mp4' + ], + w:1920, + h:1080, + protocols:[ + 2 + ], + linearity:1, + api:[ + 1, + 2 + ] + } }, + bids: [ + { + bidder: 'bizzclick', + params: { + placementId: 'hash', + accountId: 'accountId' } + } + ] + } ]; ``` \ No newline at end of file diff --git a/test/spec/modules/bizzclickBidAdapter_spec.js b/test/spec/modules/bizzclickBidAdapter_spec.js new file mode 100644 index 00000000000..39ad4ae39c9 --- /dev/null +++ b/test/spec/modules/bizzclickBidAdapter_spec.js @@ -0,0 +1,369 @@ +import { expect } from 'chai'; +import { spec } from 'modules/bizzclickBidAdapter.js'; + +const NATIVE_BID_REQUEST = { + code: 'native_example', + mediaTypes: { + native: { + title: { + required: true, + len: 800 + }, + image: { + required: true, + len: 80 + }, + sponsoredBy: { + required: true + }, + clickUrl: { + required: true + }, + privacyLink: { + required: false + }, + body: { + required: true + }, + icon: { + required: true, + sizes: [50, 50] + } + } + }, + bidder: 'bizzclick', + params: { + placementId: 'hash', + accountId: 'accountId' + }, + timeout: 1000 + +}; + +const BANNER_BID_REQUEST = { + code: 'banner_example', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bidder: 'bizzclick', + params: { + placementId: 'hash', + accountId: 'accountId' + }, + timeout: 1000, + +} + +const bidRequest = { + refererInfo: { + referer: 'test.com' + } +} + +const VIDEO_BID_REQUEST = { + code: 'video1', + sizes: [640, 480], + mediaTypes: { video: { + minduration: 0, + maxduration: 999, + boxingallowed: 1, + skip: 0, + mimes: [ + 'application/javascript', + 'video/mp4' + ], + w: 1920, + h: 1080, + protocols: [ + 2 + ], + linearity: 1, + api: [ + 1, + 2 + ] + } + }, + + bidder: 'bizzclick', + params: { + placementId: 'hash', + accountId: 'accountId' + }, + timeout: 1000 + +} + +const BANNER_BID_RESPONSE = { + id: 'request_id', + bidid: 'request_imp_id', + seatbid: [{ + bid: [{ + id: 'bid_id', + impid: 'request_imp_id', + price: 5, + adomain: ['example.com'], + adm: 'admcode', + crid: 'crid', + ext: { + mediaType: 'banner' + } + }], + }], +}; + +const VIDEO_BID_RESPONSE = { + id: 'request_id', + bidid: 'request_imp_id', + seatbid: [{ + bid: [{ + id: 'bid_id', + impid: 'request_imp_id', + price: 5, + adomain: ['example.com'], + adm: 'admcode', + crid: 'crid', + ext: { + mediaType: 'video', + vastUrl: 'http://example.vast', + } + }], + }], +}; + +let imgData = { + url: `https://example.com/image`, + w: 1200, + h: 627 +}; + +const NATIVE_BID_RESPONSE = { + id: 'request_id', + bidid: 'request_imp_id', + seatbid: [{ + bid: [{ + id: 'bid_id', + impid: 'request_imp_id', + price: 5, + adomain: ['example.com'], + adm: { native: + { + assets: [ + {id: 0, title: 'dummyText'}, + {id: 3, image: imgData}, + { + id: 5, + data: {value: 'organization.name'} + } + ], + link: {url: 'example.com'}, + imptrackers: ['tracker1.com', 'tracker2.com', 'tracker3.com'], + jstracker: 'tracker1.com' + } + }, + crid: 'crid', + ext: { + mediaType: 'native' + } + }], + }], +}; + +describe('BizzclickAdapter', function() { + describe('isBidRequestValid', function() { + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(NATIVE_BID_REQUEST)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let bid = Object.assign({}, NATIVE_BID_REQUEST); + delete bid.params; + bid.params = { + 'IncorrectParam': 0 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('build Native Request', function () { + const request = spec.buildRequests([NATIVE_BID_REQUEST], bidRequest); + + it('Creates a ServerRequest object with method, URL and data', function () { + expect(request).to.exist; + expect(request.method).to.exist; + expect(request.url).to.exist; + expect(request.data).to.exist; + }); + + it('sends bid request to our endpoint via POST', function () { + expect(request.method).to.equal('POST'); + }); + + it('Returns valid URL', function () { + expect(request.url).to.equal('https://us-e-node1.bizzclick.com/bid?rtb_seat_id=prebidjs&secret_key=accountId'); + }); + + it('Returns empty data if no valid requests are passed', function () { + let serverRequest = spec.buildRequests([]); + expect(serverRequest).to.be.an('array').that.is.empty; + }); + }); + + describe('build Banner Request', function () { + const request = spec.buildRequests([BANNER_BID_REQUEST]); + + it('Creates a ServerRequest object with method, URL and data', function () { + expect(request).to.exist; + expect(request.method).to.exist; + expect(request.url).to.exist; + expect(request.data).to.exist; + }); + + it('sends bid request to our endpoint via POST', function () { + expect(request.method).to.equal('POST'); + }); + + it('Returns valid URL', function () { + expect(request.url).to.equal('https://us-e-node1.bizzclick.com/bid?rtb_seat_id=prebidjs&secret_key=accountId'); + }); + }); + + describe('build Video Request', function () { + const request = spec.buildRequests([VIDEO_BID_REQUEST]); + + it('Creates a ServerRequest object with method, URL and data', function () { + expect(request).to.exist; + expect(request.method).to.exist; + expect(request.url).to.exist; + expect(request.data).to.exist; + }); + + it('sends bid request to our endpoint via POST', function () { + expect(request.method).to.equal('POST'); + }); + + it('Returns valid URL', function () { + expect(request.url).to.equal('https://us-e-node1.bizzclick.com/bid?rtb_seat_id=prebidjs&secret_key=accountId'); + }); + }); + + describe('interpretResponse', function () { + it('Empty response must return empty array', function() { + const emptyResponse = null; + let response = spec.interpretResponse(emptyResponse); + + expect(response).to.be.an('array').that.is.empty; + }) + + it('Should interpret banner response', function () { + const bannerResponse = { + body: [BANNER_BID_RESPONSE] + } + + const expectedBidResponse = { + requestId: BANNER_BID_RESPONSE.id, + cpm: BANNER_BID_RESPONSE.seatbid[0].bid[0].price, + width: BANNER_BID_RESPONSE.seatbid[0].bid[0].w, + height: BANNER_BID_RESPONSE.seatbid[0].bid[0].h, + ttl: BANNER_BID_RESPONSE.ttl || 1200, + currency: BANNER_BID_RESPONSE.cur || 'USD', + netRevenue: true, + creativeId: BANNER_BID_RESPONSE.seatbid[0].bid[0].crid, + dealId: BANNER_BID_RESPONSE.seatbid[0].bid[0].dealid, + mediaType: 'banner', + ad: BANNER_BID_RESPONSE.seatbid[0].bid[0].adm + } + + let bannerResponses = spec.interpretResponse(bannerResponse); + + 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'); + expect(dataItem.requestId).to.equal(expectedBidResponse.requestId); + expect(dataItem.cpm).to.equal(expectedBidResponse.cpm); + expect(dataItem.ad).to.equal(expectedBidResponse.ad); + expect(dataItem.ttl).to.equal(expectedBidResponse.ttl); + expect(dataItem.creativeId).to.equal(expectedBidResponse.creativeId); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal(expectedBidResponse.currency); + expect(dataItem.width).to.equal(expectedBidResponse.width); + expect(dataItem.height).to.equal(expectedBidResponse.height); + }); + + it('Should interpret video response', function () { + const videoResponse = { + body: [VIDEO_BID_RESPONSE] + } + + const expectedBidResponse = { + requestId: VIDEO_BID_RESPONSE.id, + cpm: VIDEO_BID_RESPONSE.seatbid[0].bid[0].price, + width: VIDEO_BID_RESPONSE.seatbid[0].bid[0].w, + height: VIDEO_BID_RESPONSE.seatbid[0].bid[0].h, + ttl: VIDEO_BID_RESPONSE.ttl || 1200, + currency: VIDEO_BID_RESPONSE.cur || 'USD', + netRevenue: true, + creativeId: VIDEO_BID_RESPONSE.seatbid[0].bid[0].crid, + dealId: VIDEO_BID_RESPONSE.seatbid[0].bid[0].dealid, + mediaType: 'video', + vastXml: VIDEO_BID_RESPONSE.seatbid[0].bid[0].adm, + vastUrl: VIDEO_BID_RESPONSE.seatbid[0].bid[0].ext.vastUrl + } + + let videoResponses = spec.interpretResponse(videoResponse); + + expect(videoResponses).to.be.an('array').that.is.not.empty; + let dataItem = videoResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'vastXml', 'vastUrl', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType'); + expect(dataItem.requestId).to.equal(expectedBidResponse.requestId); + expect(dataItem.cpm).to.equal(expectedBidResponse.cpm); + expect(dataItem.vastXml).to.equal(expectedBidResponse.vastXml) + expect(dataItem.ttl).to.equal(expectedBidResponse.ttl); + expect(dataItem.creativeId).to.equal(expectedBidResponse.creativeId); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal(expectedBidResponse.currency); + expect(dataItem.width).to.equal(expectedBidResponse.width); + expect(dataItem.height).to.equal(expectedBidResponse.height); + }); + + it('Should interpret native response', function () { + const nativeResponse = { + body: [NATIVE_BID_RESPONSE] + } + + const expectedBidResponse = { + requestId: NATIVE_BID_RESPONSE.id, + cpm: NATIVE_BID_RESPONSE.seatbid[0].bid[0].price, + width: NATIVE_BID_RESPONSE.seatbid[0].bid[0].w, + height: NATIVE_BID_RESPONSE.seatbid[0].bid[0].h, + ttl: NATIVE_BID_RESPONSE.ttl || 1200, + currency: NATIVE_BID_RESPONSE.cur || 'USD', + netRevenue: true, + creativeId: NATIVE_BID_RESPONSE.seatbid[0].bid[0].crid, + dealId: NATIVE_BID_RESPONSE.seatbid[0].bid[0].dealid, + mediaType: 'native', + native: {clickUrl: NATIVE_BID_RESPONSE.seatbid[0].bid[0].adm.native.link.url} + } + + let nativeResponses = spec.interpretResponse(nativeResponse); + + expect(nativeResponses).to.be.an('array').that.is.not.empty; + let dataItem = nativeResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'native', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType'); + expect(dataItem.requestId).to.equal(expectedBidResponse.requestId); + expect(dataItem.cpm).to.equal(expectedBidResponse.cpm); + expect(dataItem.native.clickUrl).to.equal(expectedBidResponse.native.clickUrl) + expect(dataItem.ttl).to.equal(expectedBidResponse.ttl); + expect(dataItem.creativeId).to.equal(expectedBidResponse.creativeId); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal(expectedBidResponse.currency); + expect(dataItem.width).to.equal(expectedBidResponse.width); + expect(dataItem.height).to.equal(expectedBidResponse.height); + }); + }); +}) From 172df7f455fe2bf0605026ab0e21af1c5edf9be3 Mon Sep 17 00:00:00 2001 From: Jaimin Panchal <7393273+jaiminpanchal27@users.noreply.github.com> Date: Thu, 19 Nov 2020 12:24:38 -0500 Subject: [PATCH 033/103] Appnexus: Update maintainer (#5987) * Update maintainer * change maintainer Co-authored-by: Jaimin Panchal Co-authored-by: fawke --- modules/appnexusBidAdapter.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/appnexusBidAdapter.md b/modules/appnexusBidAdapter.md index 6ec40e83b41..d1f61836297 100644 --- a/modules/appnexusBidAdapter.md +++ b/modules/appnexusBidAdapter.md @@ -3,7 +3,7 @@ ``` Module Name: Appnexus Bid Adapter Module Type: Bidder Adapter -Maintainer: info@prebid.org +Maintainer: prebid-js@xandr.com ``` # Description From 578213ccb52f5db858026b436f5c249e6f9e418e Mon Sep 17 00:00:00 2001 From: Stephen Johnston Date: Thu, 19 Nov 2020 19:34:23 -0500 Subject: [PATCH 034/103] Fix import warning for webpack 5 (#5933) Webpack 5 emits a warning when importing named exports from json files: ``` "WARNING in ../../node_modules/prebid.js/src/secureCreatives.js 15:16-30 Should not import the named export 'EVENTS'.'BID_WON' (imported as 'EVENTS') from default-exporting module (only default export is available soon)" ``` Co-authored-by: Garth Poitras <411908+gpoitch@users.noreply.github.com> Co-authored-by: gpoitch --- src/secureCreatives.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/secureCreatives.js b/src/secureCreatives.js index 34de2be275c..cb192dd773e 100644 --- a/src/secureCreatives.js +++ b/src/secureCreatives.js @@ -5,14 +5,14 @@ import events from './events.js'; import { fireNativeTrackers, getAssetMessage } from './native.js'; -import { EVENTS } from './constants.json'; +import constants from './constants.json'; import { logWarn, replaceAuctionPrice } from './utils.js'; import { auctionManager } from './auctionManager.js'; import find from 'core-js-pure/features/array/find.js'; import { isRendererRequired, executeRenderer } from './Renderer.js'; import includes from 'core-js-pure/features/array/includes.js'; -const BID_WON = EVENTS.BID_WON; +const BID_WON = constants.EVENTS.BID_WON; export function listenMessagesFromCreative() { window.addEventListener('message', receiveMessage, false); From c98a6334011bb3738885b2240cc8687170ec9bf7 Mon Sep 17 00:00:00 2001 From: Anand Venkatraman Date: Sat, 21 Nov 2020 02:04:35 +0530 Subject: [PATCH 035/103] PulsePoint Adapter: Fixing issue with multi-format requests (#5995) * ET-1691: Pulsepoint Analytics adapter for Prebid. (#1) * ET-1691: Adding pulsepoint analytics and tests for pulsepoint adapter * ET-1691: Adding pulsepoint analytics and tests for pulsepoint adapter * ET-1691: cleanup * ET-1691: minor * ET-1691: revert package.json change * Adding bidRequest to bidFactory.createBid method as per https://github.com/prebid/Prebid.js/issues/509 * ET-1765: Adding support for additional params in PulsePoint adapter (#2) * ET-1850: Fixing https://github.com/prebid/Prebid.js/issues/866 * Minor fix * Adding mandatory parameters to Bid * APPS-3793: Fixing multi-format request issue * Added test --- modules/pulsepointBidAdapter.js | 10 ++-- .../spec/modules/pulsepointBidAdapter_spec.js | 54 +++++++++++++++++++ 2 files changed, 59 insertions(+), 5 deletions(-) diff --git a/modules/pulsepointBidAdapter.js b/modules/pulsepointBidAdapter.js index 005eadaa390..f74d79a3dc5 100644 --- a/modules/pulsepointBidAdapter.js +++ b/modules/pulsepointBidAdapter.js @@ -121,10 +121,7 @@ function bidResponseAvailable(request, response) { netRevenue: DEFAULT_NET_REVENUE, currency: bidResponse.cur || DEFAULT_CURRENCY }; - if (idToImpMap[id]['native']) { - bid['native'] = nativeResponse(idToImpMap[id], idToBidMap[id]); - bid.mediaType = 'native'; - } else if (idToImpMap[id].video) { + if (idToImpMap[id].video) { // for outstream, a renderer is specified if (idToSlotConfig[id] && utils.deepAccess(idToSlotConfig[id], 'mediaTypes.video.context') === 'outstream') { bid.renderer = outstreamRenderer(utils.deepAccess(idToSlotConfig[id], 'renderer.options'), utils.deepAccess(idToBidMap[id], 'ext.outstream')); @@ -133,10 +130,13 @@ function bidResponseAvailable(request, response) { bid.mediaType = 'video'; bid.width = idToBidMap[id].w; bid.height = idToBidMap[id].h; - } else { + } else if (idToImpMap[id].banner) { bid.ad = idToBidMap[id].adm; bid.width = idToBidMap[id].w || idToImpMap[id].banner.w; bid.height = idToBidMap[id].h || idToImpMap[id].banner.h; + } else if (idToImpMap[id]['native']) { + bid['native'] = nativeResponse(idToImpMap[id], idToBidMap[id]); + bid.mediaType = 'native'; } bids.push(bid); } diff --git a/test/spec/modules/pulsepointBidAdapter_spec.js b/test/spec/modules/pulsepointBidAdapter_spec.js index cf81a26eebb..c3830d5cb46 100644 --- a/test/spec/modules/pulsepointBidAdapter_spec.js +++ b/test/spec/modules/pulsepointBidAdapter_spec.js @@ -724,4 +724,58 @@ describe('PulsePoint Adapter Tests', function () { expect(bid.width).to.equal(728); expect(bid.height).to.equal(90); }); + it('Verify multi-format response', function () { + const bidRequests = deepClone(slotConfigs); + bidRequests[0].mediaTypes['native'] = { + title: { + required: true + }, + image: { + required: true + }, + sponsoredBy: { + required: true + } + }; + bidRequests[1].params.video = { + w: 400, + h: 300, + minduration: 5, + maxduration: 10, + }; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request).to.be.not.null; + expect(request.data).to.be.not.null; + const ortbRequest = request.data; + expect(ortbRequest.imp).to.have.lengthOf(2); + // adsize on response + const ortbResponse = { + seatbid: [{ + bid: [{ + impid: ortbRequest.imp[0].id, + price: 1.25, + adm: 'This is an Ad', + crid: 'Creative#123', + w: 728, + h: 90 + }, { + impid: ortbRequest.imp[1].id, + price: 2.5, + adm: '', + crid: 'Creative#234', + w: 728, + h: 90 + }] + }] + }; + // request has both types - banner and native, response is parsed as banner. + // for impression#2, response is parsed as video + const bids = spec.interpretResponse({ body: ortbResponse }, request); + expect(bids).to.have.lengthOf(2); + const bid = bids[0]; + expect(bid.width).to.equal(728); + expect(bid.height).to.equal(90); + const secondBid = bids[1]; + expect(secondBid.vastXml).to.equal(''); + }); }); From 1e98aef06bbe470838ee67779756747e962bdcec Mon Sep 17 00:00:00 2001 From: relaido <63339139+relaido@users.noreply.github.com> Date: Sat, 21 Nov 2020 05:37:14 +0900 Subject: [PATCH 036/103] Fix/test code set cookie (#5996) * add relaido adapter * remove event listener * fixed UserSyncs and e.data * fix conflicts * use storage Class Co-authored-by: ishigami_shingo Co-authored-by: cmertv-sishigami Co-authored-by: t_bun --- test/spec/modules/relaidoBidAdapter_spec.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/spec/modules/relaidoBidAdapter_spec.js b/test/spec/modules/relaidoBidAdapter_spec.js index 65dcd9b7db7..ebc62752f16 100644 --- a/test/spec/modules/relaidoBidAdapter_spec.js +++ b/test/spec/modules/relaidoBidAdapter_spec.js @@ -15,7 +15,6 @@ const storage = getStorageManager(); storage.setCookie(UUID_KEY, relaido_uuid); describe('RelaidoAdapter', function () { - window.document.cookie = `${UUID_KEY}=${relaido_uuid}` let bidRequest; let bidderRequest; let serverResponse; From 04c941fcff2d4fb0581e363c81df4753f90be0af Mon Sep 17 00:00:00 2001 From: Monis Qadri Date: Sat, 21 Nov 2020 03:55:13 +0530 Subject: [PATCH 037/103] Honour bidFloor key in params (#5999) Co-authored-by: monis.q --- modules/medianetBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/medianetBidAdapter.js b/modules/medianetBidAdapter.js index a30f6fc2627..a2dc8bdfd03 100644 --- a/modules/medianetBidAdapter.js +++ b/modules/medianetBidAdapter.js @@ -204,7 +204,7 @@ function slotParams(bidRequest) { params.tagid = bidRequest.params.crid.toString(); } - let bidFloor = parseFloat(bidRequest.params.bidfloor); + let bidFloor = parseFloat(bidRequest.params.bidfloor || bidRequest.params.bidFloor); if (bidFloor) { params.bidfloor = bidFloor; } From 8135e5d0045dfd3a045164f29624837a7c77289b Mon Sep 17 00:00:00 2001 From: Jurij Sinickij Date: Sat, 21 Nov 2020 07:47:37 +0200 Subject: [PATCH 038/103] adform adapter - allow to pass custom eids param (#6008) --- modules/adformBidAdapter.js | 8 ++------ test/spec/modules/adformBidAdapter_spec.js | 8 ++++++++ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/modules/adformBidAdapter.js b/modules/adformBidAdapter.js index 48000e082b2..05e45a428d3 100644 --- a/modules/adformBidAdapter.js +++ b/modules/adformBidAdapter.js @@ -23,7 +23,7 @@ export const spec = { const eids = getEncodedEIDs(utils.deepAccess(validBidRequests, '0.userIdAsEids')); var request = []; - var globalParams = [ [ 'adxDomain', 'adx.adform.net' ], [ 'fd', 1 ], [ 'url', null ], [ 'tid', null ] ]; + var globalParams = [ [ 'adxDomain', 'adx.adform.net' ], [ 'fd', 1 ], [ 'url', null ], [ 'tid', null ], [ 'eids', eids ] ]; var bids = JSON.parse(JSON.stringify(validBidRequests)); var bidder = (bids[0] && bids[0].bidder) || BIDDER_CODE; for (i = 0, l = bids.length; i < l; i++) { @@ -65,10 +65,6 @@ export const spec = { request.push('us_privacy=' + bidderRequest.uspConsent); } - if (eids) { - request.push('eids=' + eids); - } - for (i = 1, l = globalParams.length; i < l; i++) { _key = globalParams[i][0]; _value = globalParams[i][1]; @@ -100,7 +96,7 @@ export const spec = { function getEncodedEIDs(eids) { if (utils.isArray(eids) && eids.length > 0) { const parsed = parseEIDs(eids); - return encodeURIComponent(btoa(JSON.stringify(parsed))); + return btoa(JSON.stringify(parsed)); } } diff --git a/test/spec/modules/adformBidAdapter_spec.js b/test/spec/modules/adformBidAdapter_spec.js index 360979659de..23db7a8dc97 100644 --- a/test/spec/modules/adformBidAdapter_spec.js +++ b/test/spec/modules/adformBidAdapter_spec.js @@ -149,6 +149,14 @@ describe('Adform adapter', function () { }); }); + it('should allow to pass custom extended ids', function () { + bids[0].params.eids = 'some_id_value'; + let request = spec.buildRequests(bids); + let eids = parseUrl(request.url).query.eids; + + assert.equal(eids, 'some_id_value'); + }); + describe('user privacy', function () { it('should send GDPR Consent data to adform if gdprApplies', function () { let request = spec.buildRequests([bids[0]], {gdprConsent: {gdprApplies: true, consentString: 'concentDataString'}}); From e5e899d2a8fca90f7b03e8e72e1d910a640cc6a0 Mon Sep 17 00:00:00 2001 From: John Salis Date: Sat, 21 Nov 2020 00:58:14 -0500 Subject: [PATCH 039/103] Add video response type param to Beachfront adapter (#6011) * add video response type param to beachfront bidder * run tests Co-authored-by: John Salis --- modules/beachfrontBidAdapter.js | 19 +++++--- .../spec/modules/beachfrontBidAdapter_spec.js | 43 ++++++++++++------- 2 files changed, 41 insertions(+), 21 deletions(-) diff --git a/modules/beachfrontBidAdapter.js b/modules/beachfrontBidAdapter.js index 5f0a4b03a04..4b30f47e2cf 100644 --- a/modules/beachfrontBidAdapter.js +++ b/modules/beachfrontBidAdapter.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.13'; +const ADAPTER_VERSION = '1.14'; const ADAPTER_NAME = 'BFIO_PREBID'; const OUTSTREAM = 'outstream'; @@ -61,18 +61,17 @@ export const spec = { response = response.body; if (isVideoBid(bidRequest)) { - if (!response || !response.url || !response.bidPrice) { + if (!response || !response.bidPrice) { utils.logWarn(`No valid video bids from ${spec.code} bidder`); return []; } let sizes = getVideoSizes(bidRequest); let firstSize = getFirstSize(sizes); let context = utils.deepAccess(bidRequest, 'mediaTypes.video.context'); - return { + let responseType = getVideoBidParam(bidRequest, 'responseType') || 'both'; + let bidResponse = { requestId: bidRequest.bidId, bidderCode: spec.code, - vastUrl: response.url, - vastXml: response.vast, cpm: response.bidPrice, width: firstSize.w, height: firstSize.h, @@ -83,6 +82,16 @@ export const spec = { netRevenue: true, ttl: 300 }; + + if (responseType === 'nurl' || responseType === 'both') { + bidResponse.vastUrl = response.url; + } + + if (responseType === 'adm' || responseType === 'both') { + bidResponse.vastXml = response.vast; + } + + return bidResponse; } else { if (!response || !response.length) { utils.logWarn(`No valid banner bids from ${spec.code} bidder`); diff --git a/test/spec/modules/beachfrontBidAdapter_spec.js b/test/spec/modules/beachfrontBidAdapter_spec.js index aa952d088a7..661780ffac0 100644 --- a/test/spec/modules/beachfrontBidAdapter_spec.js +++ b/test/spec/modules/beachfrontBidAdapter_spec.js @@ -530,16 +530,6 @@ describe('BeachfrontAdapter', function () { expect(bidResponse.length).to.equal(0); }); - it('should return no bids if the response "url" is missing', function () { - const bidRequest = bidRequests[0]; - bidRequest.mediaTypes = { video: {} }; - const serverResponse = { - bidPrice: 5.00 - }; - const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); - expect(bidResponse.length).to.equal(0); - }); - it('should return no bids if the response "bidPrice" is missing', function () { const bidRequest = bidRequests[0]; bidRequest.mediaTypes = { video: {} }; @@ -562,6 +552,7 @@ describe('BeachfrontAdapter', function () { const serverResponse = { bidPrice: 5.00, url: 'http://reachms.bfmio.com/getmu?aid=bid:19c4a196-fb21-4c81-9a1a-ecc5437a39da', + vast: '', crid: '123abc' }; const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); @@ -571,7 +562,7 @@ describe('BeachfrontAdapter', function () { cpm: serverResponse.bidPrice, creativeId: serverResponse.crid, vastUrl: serverResponse.url, - vastXml: undefined, + vastXml: serverResponse.vast, width: width, height: height, renderer: null, @@ -602,7 +593,7 @@ describe('BeachfrontAdapter', function () { }); }); - it('should return vast xml if found on the bid response', () => { + it('should return only vast url if the response type is "nurl"', () => { const width = 640; const height = 480; const bidRequest = bidRequests[0]; @@ -611,6 +602,7 @@ describe('BeachfrontAdapter', function () { playerSize: [ width, height ] } }; + bidRequest.params.video = { responseType: 'nurl' }; const serverResponse = { bidPrice: 5.00, url: 'http://reachms.bfmio.com/getmu?aid=bid:19c4a196-fb21-4c81-9a1a-ecc5437a39da', @@ -618,10 +610,29 @@ describe('BeachfrontAdapter', function () { crid: '123abc' }; const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); - expect(bidResponse).to.deep.contain({ - vastUrl: serverResponse.url, - vastXml: serverResponse.vast - }); + expect(bidResponse.vastUrl).to.equal(serverResponse.url); + expect(bidResponse.vastXml).to.equal(undefined); + }); + + it('should return only vast xml if the response type is "adm"', () => { + const width = 640; + const height = 480; + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { + video: { + playerSize: [ width, height ] + } + }; + bidRequest.params.video = { responseType: 'adm' }; + const serverResponse = { + bidPrice: 5.00, + url: 'http://reachms.bfmio.com/getmu?aid=bid:19c4a196-fb21-4c81-9a1a-ecc5437a39da', + vast: '', + crid: '123abc' + }; + const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); + expect(bidResponse.vastUrl).to.equal(undefined); + expect(bidResponse.vastXml).to.equal(serverResponse.vast); }); it('should return a renderer for outstream video bids', function () { From 022bcea3e9c72a9a2817c82484adad51fda8dcc0 Mon Sep 17 00:00:00 2001 From: Anthony Lauzon Date: Sat, 21 Nov 2020 01:26:35 -0500 Subject: [PATCH 040/103] fix appnexus segment field format (#6013) --- modules/haloRtdProvider.js | 5 ++--- test/spec/modules/haloRtdProvider_spec.js | 24 +++++++++++------------ 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/modules/haloRtdProvider.js b/modules/haloRtdProvider.js index fff9e43ea2a..1ce44ca6004 100644 --- a/modules/haloRtdProvider.js +++ b/modules/haloRtdProvider.js @@ -33,9 +33,8 @@ const segmentMappers = { set(bid, 'params.user.segments', []); let appnexusSegments = []; segments.forEach(segment => { - if (typeof segment.value != 'undefined' && segment.value != null) { - let appnexusSegment = {'id': segment.id, 'value': segment.value}; - appnexusSegments.push(appnexusSegment); + if (typeof segment.id != 'undefined' && segment.id != null) { + appnexusSegments.push(parseInt(segment.id)); } }) bid.params.user.segments = bid.params.user.segments.concat(appnexusSegments); diff --git a/test/spec/modules/haloRtdProvider_spec.js b/test/spec/modules/haloRtdProvider_spec.js index 17c2e19e1a6..69ea4bc997c 100644 --- a/test/spec/modules/haloRtdProvider_spec.js +++ b/test/spec/modules/haloRtdProvider_spec.js @@ -34,7 +34,7 @@ describe('haloRtdProvider', function() { id: 'appnexus', segment: [ { - id: 'apnseg0' + id: '0' } ] } @@ -43,7 +43,7 @@ describe('haloRtdProvider', function() { }, params: { user: { - segments: [{'id': 'apnseg0', 'value': 0}] + segments: [0] } } } @@ -82,22 +82,22 @@ describe('haloRtdProvider', function() { ]; const data = { - appnexus: [{id: 'apnseg1', value: 0}, {id: 'apnseg2', value: 2}, {id: 'apnseg3'}], + appnexus: [{id: '1'}, {id: '2'}, {id: '3'}], generic: [{id: 'seg1'}, {id: 'seg2'}, {id: 'seg3'}] }; addSegmentData(adUnits, data, config); - expect(adUnits[0].bids[0].fpd.user.data[0].segment[0]).to.have.deep.property('id', 'apnseg0'); - expect(adUnits[0].bids[0].fpd.user.data[0].segment[1]).to.have.deep.property('id', 'apnseg1'); - expect(adUnits[0].bids[0].fpd.user.data[0].segment[2]).to.have.deep.property('id', 'apnseg2'); - expect(adUnits[0].bids[0].fpd.user.data[0].segment[3]).to.have.deep.property('id', 'apnseg3'); - expect(adUnits[0].bids[0].params.user).to.have.deep.property('segments', [{'id': 'apnseg0', 'value': 0}, {'id': 'apnseg1', 'value': 0}, {'id': 'apnseg2', 'value': 2}]); + expect(adUnits[0].bids[0].fpd.user.data[0].segment[0]).to.have.deep.property('id', '0'); + expect(adUnits[0].bids[0].fpd.user.data[0].segment[1]).to.have.deep.property('id', '1'); + expect(adUnits[0].bids[0].fpd.user.data[0].segment[2]).to.have.deep.property('id', '2'); + expect(adUnits[0].bids[0].fpd.user.data[0].segment[3]).to.have.deep.property('id', '3'); + expect(adUnits[0].bids[0].params.user).to.have.deep.property('segments', [0, 1, 2, 3]); - expect(adUnits[1].bids[0].fpd.user.data[0].segment[0]).to.have.deep.property('id', 'apnseg1'); - expect(adUnits[1].bids[0].fpd.user.data[0].segment[1]).to.have.deep.property('id', 'apnseg2'); - expect(adUnits[1].bids[0].fpd.user.data[0].segment[2]).to.have.deep.property('id', 'apnseg3'); - expect(adUnits[1].bids[0].params.user).to.have.deep.property('segments', [{'id': 'apnseg1', 'value': 0}, {'id': 'apnseg2', 'value': 2}]); + expect(adUnits[1].bids[0].fpd.user.data[0].segment[0]).to.have.deep.property('id', '1'); + expect(adUnits[1].bids[0].fpd.user.data[0].segment[1]).to.have.deep.property('id', '2'); + expect(adUnits[1].bids[0].fpd.user.data[0].segment[2]).to.have.deep.property('id', '3'); + expect(adUnits[1].bids[0].params.user).to.have.deep.property('segments', [1, 2, 3]); expect(adUnits[1].bids[1].fpd.user.data[0].segment[0]).to.have.deep.property('id', 'seg1'); expect(adUnits[1].bids[1].fpd.user.data[0].segment[1]).to.have.deep.property('id', 'seg2'); From cebae4a07748bcf0570e1781c31bb9f865bffbab Mon Sep 17 00:00:00 2001 From: haxmediagithub <74675750+haxmediagithub@users.noreply.github.com> Date: Mon, 23 Nov 2020 18:06:30 +0200 Subject: [PATCH 041/103] New haxmedia bidder adapter (#6001) --- modules/haxmediaBidAdapter.js | 107 +++++++ modules/haxmediaBidAdapter.md | 72 +++++ test/spec/modules/haxmediaBidAdapter_spec.js | 304 +++++++++++++++++++ 3 files changed, 483 insertions(+) create mode 100644 modules/haxmediaBidAdapter.js create mode 100644 modules/haxmediaBidAdapter.md create mode 100644 test/spec/modules/haxmediaBidAdapter_spec.js diff --git a/modules/haxmediaBidAdapter.js b/modules/haxmediaBidAdapter.js new file mode 100644 index 00000000000..c4ce2eb3663 --- /dev/null +++ b/modules/haxmediaBidAdapter.js @@ -0,0 +1,107 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import * as utils from '../src/utils.js'; + +const BIDDER_CODE = 'haxmedia'; +const AD_URL = 'https://balancer.haxmedia.io/?c=o&m=multi'; + +function isBidResponseValid(bid) { + if (!bid.requestId || !bid.cpm || !bid.creativeId || + !bid.ttl || !bid.currency) { + return false; + } + switch (bid.mediaType) { + case BANNER: + return Boolean(bid.width && bid.height && bid.ad); + case VIDEO: + return Boolean(bid.vastUrl); + case NATIVE: + return Boolean(bid.native && bid.native.impressionTrackers); + default: + return false; + } +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: (bid) => { + return Boolean(bid.bidId && bid.params && !isNaN(parseInt(bid.params.placementId))); + }, + + buildRequests: (validBidRequests = [], bidderRequest) => { + let winTop = window; + let location; + try { + location = new URL(bidderRequest.refererInfo.referer) + winTop = window.top; + } catch (e) { + location = winTop.location; + utils.logMessage(e); + }; + + const placements = []; + const request = { + 'deviceWidth': winTop.screen.width, + 'deviceHeight': winTop.screen.height, + 'language': (navigator && navigator.language) ? navigator.language.split('-')[0] : '', + 'secure': 1, + 'host': location.host, + 'page': location.pathname, + 'placements': placements + }; + + if (bidderRequest) { + if (bidderRequest.uspConsent) { + request.ccpa = bidderRequest.uspConsent; + } + if (bidderRequest.gdprConsent) { + request.gdpr = bidderRequest.gdprConsent + } + } + + const len = validBidRequests.length; + for (let i = 0; i < len; i++) { + const bid = validBidRequests[i]; + const placement = { + placementId: bid.params.placementId, + bidId: bid.bidId, + schain: bid.schain || {}, + }; + const mediaType = bid.mediaTypes + + if (mediaType && mediaType[BANNER] && mediaType[BANNER].sizes) { + placement.sizes = mediaType[BANNER].sizes; + placement.traffic = BANNER; + } else if (mediaType && mediaType[VIDEO] && mediaType[VIDEO].playerSize) { + placement.wPlayer = mediaType[VIDEO].playerSize[0]; + placement.hPlayer = mediaType[VIDEO].playerSize[1]; + placement.traffic = VIDEO; + } else if (mediaType && mediaType[NATIVE]) { + placement.native = mediaType[NATIVE]; + placement.traffic = NATIVE; + } + placements.push(placement); + } + + return { + method: 'POST', + url: AD_URL, + data: request + }; + }, + + interpretResponse: (serverResponse) => { + let response = []; + for (let i = 0; i < serverResponse.body.length; i++) { + let resItem = serverResponse.body[i]; + if (isBidResponseValid(resItem)) { + response.push(resItem); + } + } + return response; + }, +}; + +registerBidder(spec); diff --git a/modules/haxmediaBidAdapter.md b/modules/haxmediaBidAdapter.md new file mode 100644 index 00000000000..f661a9e4e71 --- /dev/null +++ b/modules/haxmediaBidAdapter.md @@ -0,0 +1,72 @@ +# Overview + +``` +Module Name: haxmedia Bidder Adapter +Module Type: haxmedia Bidder Adapter +Maintainer: haxmixqk@haxmediapartners.io +``` + +# Description + +Module that connects to haxmedia demand sources + +# Test Parameters +``` + var adUnits = [ + { + code:'1', + mediaTypes:{ + banner: { + sizes: [[300, 250]], + } + }, + bids:[ + { + bidder: 'haxmedia', + params: { + placementId: 0 + } + } + ] + }, + { + code:'1', + mediaTypes:{ + video: { + playerSize: [640, 480], + context: 'instream' + } + }, + bids:[ + { + bidder: 'haxmedia', + params: { + placementId: 0 + } + } + ] + }, + { + code:'1', + mediaTypes:{ + native: { + title: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + }, + bids:[ + { + bidder: 'haxmedia', + params: { + placementId: 0 + } + } + ] + } + ]; +``` diff --git a/test/spec/modules/haxmediaBidAdapter_spec.js b/test/spec/modules/haxmediaBidAdapter_spec.js new file mode 100644 index 00000000000..2e39d771bdf --- /dev/null +++ b/test/spec/modules/haxmediaBidAdapter_spec.js @@ -0,0 +1,304 @@ +import {expect} from 'chai'; +import {spec} from '../../../modules/haxmediaBidAdapter.js'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; + +describe('haxmediaBidAdapter', function () { + const bid = { + bidId: '23fhj33i987f', + bidder: 'haxmedia', + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 783, + traffic: BANNER + } + }; + + const bidderRequest = { + refererInfo: { + referer: 'test.com' + } + }; + + describe('isBidRequestValid', function () { + it('Should return true if there are bidId, params and key parameters present', function () { + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + it('Should return false if at least one of parameters is not present', function () { + delete bid.params.placementId; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + let serverRequest = spec.buildRequests([bid], 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://balancer.haxmedia.io/?c=o&m=multi'); + }); + 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', 'language', 'secure', 'host', 'page', 'placements'); + 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.gdpr).to.not.exist; + expect(data.ccpa).to.not.exist; + let placement = data['placements'][0]; + expect(placement).to.have.keys('placementId', 'bidId', 'traffic', 'sizes', 'schain'); + expect(placement.placementId).to.equal(783); + expect(placement.bidId).to.equal('23fhj33i987f'); + expect(placement.traffic).to.equal(BANNER); + expect(placement.schain).to.be.an('object'); + expect(placement.sizes).to.be.an('array'); + }); + + it('Returns valid data for mediatype video', function () { + const playerSize = [300, 300]; + bid.mediaTypes = {}; + bid.params.traffic = VIDEO; + bid.mediaTypes[VIDEO] = { + playerSize + }; + serverRequest = spec.buildRequests([bid], bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + let placement = data['placements'][0]; + expect(placement).to.be.an('object'); + expect(placement).to.have.keys('placementId', 'bidId', 'traffic', 'wPlayer', 'hPlayer', 'schain'); + expect(placement.traffic).to.equal(VIDEO); + expect(placement.wPlayer).to.equal(playerSize[0]); + expect(placement.hPlayer).to.equal(playerSize[1]); + }); + + it('Returns valid data for mediatype native', function () { + const native = { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + }; + + bid.mediaTypes = {}; + bid.params.traffic = NATIVE; + bid.mediaTypes[NATIVE] = native; + serverRequest = spec.buildRequests([bid], bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + let placement = data['placements'][0]; + expect(placement).to.be.an('object'); + expect(placement).to.have.keys('placementId', 'bidId', 'traffic', 'native', 'schain'); + expect(placement.traffic).to.equal(NATIVE); + expect(placement.native).to.equal(native); + }); + + it('Returns data with gdprConsent and without uspConsent', function () { + bidderRequest.gdprConsent = 'test'; + serverRequest = spec.buildRequests([bid], 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 = 'test'; + serverRequest = spec.buildRequests([bid], 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([]); + 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' + }] + }; + 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'); + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.4); + expect(dataItem.width).to.equal(300); + expect(dataItem.height).to.equal(250); + expect(dataItem.ad).to.equal('Test'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + }); + 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' + }] + }; + 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'); + 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'); + }); + 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', + }] + }; + 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'); + 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'); + }); + 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; + }); + }); +}); From 8930be4fc59e086bd6e0c5caf83e0dee673b4fa1 Mon Sep 17 00:00:00 2001 From: Brandon Ling <51931757+blingster7@users.noreply.github.com> Date: Tue, 24 Nov 2020 02:02:34 -0500 Subject: [PATCH 042/103] [Triplelift] Add advertiserDomains support (#5993) * Add IdentityLink support and fix UnifiedId. It appears we've been looking for UnifiedId userIds on the bidderRequest object, when they are found on bidRequests. This commit fixes that error, and adds support for IdentityLink. * change maintainer email to group * TripleLift: Sending schain (#1) * Sending schain * null -> undefined * Hardcode sync endpoint protocol * Switch to EB2 sync endpoint * Add support for image based user syncing * Rename endpoint variable * Add assertion * Add CCPA query param * Simplify check for usPrivacy argument * put advertiser name in the bid.meta field if it exists * update unit tests with meta.advertiserName field * Triplelift: FPD key value pair support (#5) * Triplelift: Add support for global fpd * don't filter fpd * adds coppa support back in * add gvlid, update validation method, add unit tests * remove advertiserDomains logic * typo * update _buildResponseObject to use new instream validation * add advertiserDomains support Co-authored-by: Will Chapin Co-authored-by: colbertk <50499465+colbertk@users.noreply.github.com> Co-authored-by: David Andersen Co-authored-by: colbertk Co-authored-by: Kevin Zhou Co-authored-by: kzhouTL <43545828+kzhouTL@users.noreply.github.com> Co-authored-by: Sy Dao Co-authored-by: sdao-tl <49252703+sdao-tl@users.noreply.github.com> --- modules/tripleliftBidAdapter.js | 4 ++++ test/spec/modules/tripleliftBidAdapter_spec.js | 10 +++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/modules/tripleliftBidAdapter.js b/modules/tripleliftBidAdapter.js index 69c52711236..d54d76efb41 100644 --- a/modules/tripleliftBidAdapter.js +++ b/modules/tripleliftBidAdapter.js @@ -293,6 +293,10 @@ function _buildResponseObject(bidderRequest, bid) { if (bid.advertiser_name) { bidResponse.meta.advertiserName = bid.advertiser_name; } + + if (bid.adomain && bid.adomain.length) { + bidResponse.meta.advertiserDomains = bid.adomain; + } }; return bidResponse; } diff --git a/test/spec/modules/tripleliftBidAdapter_spec.js b/test/spec/modules/tripleliftBidAdapter_spec.js index f01949755c7..b417876f276 100644 --- a/test/spec/modules/tripleliftBidAdapter_spec.js +++ b/test/spec/modules/tripleliftBidAdapter_spec.js @@ -638,7 +638,8 @@ describe('triplelift adapter', function () { ad: 'ad-markup', iurl: 'https://s.adroll.com/a/IYR/N36/IYRN366MFVDITBAGNNT5U6.jpg', tl_source: 'tlx', - advertiser_name: 'fake advertiser name' + advertiser_name: 'fake advertiser name', + adomain: ['basspro.com', 'internetalerts.org'] }, { imp_id: 1, @@ -747,6 +748,13 @@ describe('triplelift adapter', function () { expect(result[0].meta.advertiserName).to.equal('fake advertiser name'); expect(result[1].meta).to.not.have.key('advertiserName'); }); + + it('should include the advertiser domain array in the meta field if available', function () { + let result = tripleliftAdapterSpec.interpretResponse(response, {bidderRequest}); + expect(result[0].meta.advertiserDomains[0]).to.equal('basspro.com'); + expect(result[0].meta.advertiserDomains[1]).to.equal('internetalerts.org'); + expect(result[1].meta).to.not.have.key('advertiserDomains'); + }); }); describe('getUserSyncs', function() { From 015c48b67956348888f1768fbaae8a9027222468 Mon Sep 17 00:00:00 2001 From: omerBrowsi <54346241+omerBrowsi@users.noreply.github.com> Date: Tue, 24 Nov 2020 23:08:13 +0200 Subject: [PATCH 043/103] Browsi RTD provider docs (#5920) * real time data module, browsi sub module for real time data, new hook bidsBackCallback, fix for config unsubscribe * change timeout&primary ad server only to auctionDelay update docs * support multiple providers * change promise to callbacks configure submodule on submodules.json * bug fixes * use Prebid ajax * tests fix * browsi real time data provider improvements * real time data module, browsi sub module for real time data, new hook bidsBackCallback, fix for config unsubscribe * change timeout&primary ad server only to auctionDelay update docs * support multiple providers * change promise to callbacks configure submodule on submodules.json * bug fixes * use Prebid ajax * tests fix * browsi real time data provider improvements * RTD docs --- modules/browsiRtdProvider.md | 55 +++++++++++++++++++++++++++++++ modules/rtdModule/provider.md | 28 +++++++++------- modules/rtdModule/realTimeData.md | 32 ------------------ 3 files changed, 72 insertions(+), 43 deletions(-) create mode 100644 modules/browsiRtdProvider.md delete mode 100644 modules/rtdModule/realTimeData.md diff --git a/modules/browsiRtdProvider.md b/modules/browsiRtdProvider.md new file mode 100644 index 00000000000..0dd8c1d7609 --- /dev/null +++ b/modules/browsiRtdProvider.md @@ -0,0 +1,55 @@ +# Overview + +The Browsi RTD module provides viewability predictions for ad slots on the page. +To use this module, you’ll need to work with [Browsi](https://gobrowsi.com/) to get an account and receive instructions on how to set up your pages and ad server. + +# Configurations + +Compile the Browsi RTD Provider into your Prebid build: + +`gulp build --modules=browsiRtdProvider` + + +Configuration example for using RTD module with `browsi` provider +```javascript + pbjs.setConfig({ + "realTimeData": { + "auctionDelay": 1000, + dataProviders:[{ + "name": "browsi", + "waitForIt": "true" + "params": { + "url": "testUrl.com", + "siteKey": "testKey", + "pubKey": "testPub", + "keyName":"bv" + } + }] + } + }); +``` + +#Params + +Contact Browsi to get required params + +| param name | type |Scope | Description | +| :------------ | :------------ | :------- | :------- | +| url | string | required | Browsi server URL | +| siteKey | string | required | Site key | +| pubKey | string | required | Publisher key | +| keyName | string | optional | Ad unit targeting key | + + +#Output +`getTargetingData` function will return expected viewability prediction in the following structure: +```json +{ + "adUnitCode":{ + "browsiViewability":"0.6" + }, + "adUnitCode2":{ + "browsiViewability":"0.9" + } +} +``` diff --git a/modules/rtdModule/provider.md b/modules/rtdModule/provider.md index fb42e7188d3..116db160238 100644 --- a/modules/rtdModule/provider.md +++ b/modules/rtdModule/provider.md @@ -1,27 +1,33 @@ New provider must include the following: -1. sub module object: -``` -export const subModuleName = { - name: String, - getData: Function -}; -``` +1. sub module object with the following keys: -2. Function that returns the real time data according to the following structure: -``` +| param name | type | Scope | Description | Params | +| :------------ | :------------ | :------ | :------ | :------ | +| name | string | required | must match the name provided by the publisher in the on-page config | n/a | +| init | function | required | defines the function that does any auction-level initialization required | config, userConsent | +| getTargetingData | function | optional | defines a function that provides ad server targeting data to RTD-core | adUnitArray, config, userConsent | +| getBidRequestData | function | optional | defines a function that provides ad server targeting data to RTD-core | reqBidsConfigObj, callback, config, userConsent | +| onAuctionInitEvent | function | optional | listens to the AUCTION_INIT event and calls a sub-module function that lets it inspect and/or update the auction | auctionDetails, config, userConsent | +| onAuctionEndEvent | function |optional | listens to the AUCTION_END event and calls a sub-module function that lets it know when auction is done | auctionDetails, config, userConsent | +| onBidResponseEvent | function |optional | listens to the BID_RESPONSE event and calls a sub-module function that lets it know when a bid response has been collected | bidResponse, config, userConsent | + +2. `getTargetingData` function (if defined) should return ad unit targeting data according to the following structure: +```json { "adUnitCode":{ "key":"value", "key2":"value" }, "adUnitCode2":{ - "dataKey":"dataValue", + "dataKey":"dataValue" } } ``` 3. Hook to Real Time Data module: -``` +```javascript submodule('realTimeData', subModuleName); ``` + +4. See detailed documentation [here](https://docs.prebid.org/dev-docs/add-rtd-submodule.html) diff --git a/modules/rtdModule/realTimeData.md b/modules/rtdModule/realTimeData.md deleted file mode 100644 index b2859098b1f..00000000000 --- a/modules/rtdModule/realTimeData.md +++ /dev/null @@ -1,32 +0,0 @@ -## Real Time Data Configuration Example - -Example showing config using `browsi` sub module -``` - pbjs.setConfig({ - "realTimeData": { - "auctionDelay": 1000, - dataProviders[{ - "name": "browsi", - "params": { - "url": "testUrl.com", - "siteKey": "testKey", - "pubKey": "testPub", - "keyName":"bv" - } - }] - } - }); -``` - -Example showing real time data object received form `browsi` real time data provider -``` -{ - "adUnitCode":{ - "key":"value", - "key2":"value" - }, - "adUnitCode2":{ - "dataKey":"dataValue", - } -} -``` From 1f24ee48c906fd446178659a830cb1fe9af49532 Mon Sep 17 00:00:00 2001 From: GeoEdge-r-and-d <72186958+GeoEdge-r-and-d@users.noreply.github.com> Date: Tue, 24 Nov 2020 23:30:30 +0200 Subject: [PATCH 044/103] Add Geoedge RTD provider submodule (#5869) * Add Geoedge RTD provider submodule * Add Geoedge RTD provider submodule accroding to RTD phase 3 * Add tests * Add docs * Add integration example * Add as child module of RTD for easier builds * Update RTD submodule interface See https://docs.prebid.org/dev-docs/add-rtd-submodule.html * Update tests * Update integration example remove unnecessary param * Update docs * Update RTD submodule provider * Remove getConfig * Get params from init * Use beforeInit * Update docs Extend and document the wap param * Update tests * Remove unused config module * Update integreation example Relevant opening and inline comments * Update Geoedge RTD submodule provider * Hardcode HTTPS scheme * Rename to "donePreload" for clarity * Use regex to replace macros instead of loop * Update tests Preload request scheme is now always HTTPS * Remove integration example HTML page As for @Fawke request at https://github.com/prebid/Prebid.js/pull/5869#issuecomment-732237482 Co-authored-by: daniel manan Co-authored-by: bretg --- modules/.submodules.json | 3 +- modules/geoedgeRtdProvider.js | 213 +++++++++++++++++++ modules/geoedgeRtdProvider.md | 67 ++++++ test/spec/modules/geoedgeRtdProvider_spec.js | 111 ++++++++++ 4 files changed, 393 insertions(+), 1 deletion(-) create mode 100644 modules/geoedgeRtdProvider.js create mode 100644 modules/geoedgeRtdProvider.md create mode 100644 test/spec/modules/geoedgeRtdProvider_spec.js diff --git a/modules/.submodules.json b/modules/.submodules.json index 53caf7a0671..36ead7df888 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -28,6 +28,7 @@ "browsiRtdProvider", "haloRtdProvider", "jwplayerRtdProvider", - "reconciliationRtdProvider" + "reconciliationRtdProvider", + "geoedgeRtdProvider" ] } diff --git a/modules/geoedgeRtdProvider.js b/modules/geoedgeRtdProvider.js new file mode 100644 index 00000000000..001ef67b66a --- /dev/null +++ b/modules/geoedgeRtdProvider.js @@ -0,0 +1,213 @@ +/** + * This module adds geoedge provider to the real time data module + * The {@link module:modules/realTimeData} module is required + * The module will fetch creative wrapper from geoedge server + * The module will place geoedge RUM client on bid responses markup + * @module modules/geoedgeProvider + * @requires module:modules/realTimeData + */ + +/** + * @typedef {Object} ModuleParams + * @property {string} key + * @property {?Object} bidders + * @property {?boolean} wap + * @property {?string} keyName + */ + +import { submodule } from '../src/hook.js'; +import { ajax } from '../src/ajax.js'; +import { generateUUID, insertElement, isEmpty, logError } from '../src/utils.js'; + +/** @type {string} */ +const SUBMODULE_NAME = 'geoedge'; +/** @type {string} */ +export const WRAPPER_URL = 'https://wrappers.geoedge.be/wrapper.html'; +/** @type {string} */ +/* eslint-disable no-template-curly-in-string */ +export const HTML_PLACEHOLDER = '${creative}'; +/** @type {string} */ +const PV_ID = generateUUID(); +/** @type {string} */ +const HOST_NAME = 'https://rumcdn.geoedge.be'; +/** @type {string} */ +const FILE_NAME = 'grumi.js'; +/** @type {function} */ +export let getClientUrl = (key) => `${HOST_NAME}/${key}/${FILE_NAME}`; +/** @type {string} */ +export let wrapper +/** @type {boolean} */; +let wrapperReady; +/** @type {boolean} */; +let preloaded; + +/** + * fetches the creative wrapper + * @param {function} sucess - success callback + */ +export function fetchWrapper(success) { + if (wrapperReady) { + return success(wrapper); + } + ajax(WRAPPER_URL, success); +} + +/** + * sets the wrapper and calls preload client + * @param {string} responseText + */ +export function setWrapper(responseText) { + wrapperReady = true; + wrapper = responseText; +} + +/** + * preloads the client + * @param {string} key + */ +export function preloadClient(key) { + let link = document.createElement('link'); + link.rel = 'preload'; + link.as = 'script'; + link.href = getClientUrl(key); + link.onload = () => { preloaded = true }; + insertElement(link); +} + +/** + * creates identity function for string replace without special replacement patterns + * @param {string} str + * @return {function} + */ +function replacer(str) { + return function () { + return str; + } +} + +export function wrapHtml(wrapper, html) { + return wrapper.replace(HTML_PLACEHOLDER, replacer(html)); +} + +/** + * generate macros dictionary from bid response + * @param {Object} bid + * @param {string} key + * @return {Object} + */ +function getMacros(bid, key) { + return { + '${key}': key, + '%%ADUNIT%%': bid.adUnitCode, + '%%WIDTH%%': bid.width, + '%%HEIGHT%%': bid.height, + '%%PATTERN:hb_adid%%': bid.adId, + '%%PATTERN:hb_bidder%%': bid.bidderCode, + '%_isHb!': true, + '%_hbcid!': bid.creativeId || '', + '%%PATTERN:hb_pb%%': bid.pbHg, + '%%SITE%%': location.hostname, + '%_pimp%': PV_ID + }; +} + +/** + * replace macro placeholders in a string with values from a dictionary + * @param {string} wrapper + * @param {Object} macros + * @return {string} + */ +function replaceMacros(wrapper, macros) { + var re = new RegExp('\\' + Object.keys(macros).join('|'), 'gi'); + + return wrapper.replace(re, function(matched) { + return macros[matched]; + }); +} + +/** + * build final creative html with creative wrapper + * @param {Object} bid + * @param {string} wrapper + * @param {string} html + * @return {string} + */ +function buildHtml(bid, wrapper, html, key) { + let macros = getMacros(bid, key); + wrapper = replaceMacros(wrapper, macros); + return wrapHtml(wrapper, html); +} + +/** + * muatates the bid ad property + * @param {Object} bid + * @param {string} ad + */ +function mutateBid(bid, ad) { + bid.ad = ad; +} + +/** + * wraps a bid object with the creative wrapper + * @param {Object} bid + * @param {string} key + */ +export function wrapBidResponse(bid, key) { + let wrapped = buildHtml(bid, wrapper, bid.ad, key); + mutateBid(bid, wrapped); +} + +/** + * checks if bidder's bids should be monitored + * @param {string} bidder + * @return {boolean} + */ +function isSupportedBidder(bidder, paramsBidders) { + return isEmpty(paramsBidders) || paramsBidders[bidder] === true; +} + +/** + * checks if bid should be monitored + * @param {Object} bid + * @return {boolean} + */ +function shouldWrap(bid, params) { + let supportedBidder = isSupportedBidder(bid.bidderCode, params.bidders); + let donePreload = params.wap ? preloaded : true; + return wrapperReady && supportedBidder && donePreload; +} + +function conditionallyWrap(bidResponse, config, userConsent) { + let params = config.params; + if (shouldWrap(bidResponse, params)) { + wrapBidResponse(bidResponse, params.key); + } +} + +function init(config, userConsent) { + let params = config.params; + if (!params || !params.key) { + logError('missing key for geoedge RTD module provider'); + return false; + } + preloadClient(params.key); + return true; +} + +/** @type {RtdSubmodule} */ +export const geoedgeSubmodule = { + /** + * used to link submodule with realTimeData + * @type {string} + */ + name: SUBMODULE_NAME, + init, + onBidResponseEvent: conditionallyWrap +}; + +export function beforeInit() { + fetchWrapper(setWrapper); + submodule('realTimeData', geoedgeSubmodule); +} + +beforeInit(); diff --git a/modules/geoedgeRtdProvider.md b/modules/geoedgeRtdProvider.md new file mode 100644 index 00000000000..e4aa046a97d --- /dev/null +++ b/modules/geoedgeRtdProvider.md @@ -0,0 +1,67 @@ +## Overview + +Module Name: Geoedge Rtd provider +Module Type: Rtd Provider +Maintainer: guy.books@geoedge.com + +The Geoedge Realtime module let pusblishers to block bad ads such as automatic redirects, malware, offensive creatives and landing pages. +To use this module, you'll need to work with [Geoedge](https://www.geoedge.com/publishers-real-time-protection/) to get an account and cutomer key. + +## Integration + +1) Build the geoedge RTD module into the Prebid.js package with: + +``` +gulp build --modules=geoedgeRtdProvider,... +``` + +2) Use `setConfig` to instruct Prebid.js to initilize the geoedge module, as specified below. + +## Configuration + +This module is configured as part of the `realTimeData.dataProviders` object: + +```javascript +pbjs.setConfig({ + realTimeData: { + dataProviders: [{ + name: 'geoedge', + params: { + key: '123123', + bidders: { + 'bidderA': true, // monitor bids form this bidder + 'bidderB': false // do not monitor bids form this bidder. + }, + wap: true + } + }] + } +}); +``` + +Parameters details: + +{: .table .table-bordered .table-striped } +|Name |Type |Description |Notes | +| :------------ | :------------ | :------------ |:------------ | +|name | String | Real time data module name |Required, always 'geoedge' | +|params | Object | | | +|params.key | String | Customer key |Required, contact Geoedge to get your key | +|params.bidders | Object | Bidders to monitor |Optional, list of bidder to include / exclude from monitoring. Omitting this will monitor bids from all bidders. | +|params.wap |Boolean |Wrap after preload |Optional, defaults to `false`. Set to `true` if you want to monitor only after the module has preloaded the monitoring client. | + +## Example + +To view an integration example: + +1) in your cli run: + +``` +gulp serve --modules=appnexusBidAdapter,geoedgeRtdProvider +``` + +2) in your browser, navigate to: + +``` +http://localhost:9999/integrationExamples/gpt/geoedgeRtdProvider_example.html +``` diff --git a/test/spec/modules/geoedgeRtdProvider_spec.js b/test/spec/modules/geoedgeRtdProvider_spec.js new file mode 100644 index 00000000000..cf4e0b53fde --- /dev/null +++ b/test/spec/modules/geoedgeRtdProvider_spec.js @@ -0,0 +1,111 @@ +import * as utils from '../../../src/utils.js'; +import * as hook from '../../../src/hook.js' +import { beforeInit, geoedgeSubmodule, setWrapper, wrapper, htmlPlaceholder, WRAPPER_URL, getClientUrl } from '../../../modules/geoedgeRtdProvider.js'; +import { server } from '../../../test/mocks/xhr.js'; + +let key = '123123123'; +function makeConfig() { + return { + name: 'geoedge', + params: { + wap: false, + key: key, + bidders: { + bidderA: true, + bidderB: false + } + } + }; +} + +function mockBid(bidderCode) { + return { + 'ad': '', + 'cpm': '1.00', + 'width': 300, + 'height': 250, + 'bidderCode': bidderCode, + 'requestId': utils.getUniqueIdentifierStr(), + 'creativeId': 'id', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 360 + }; +} + +let mockWrapper = `${htmlPlaceholder}`; + +describe('Geoedge RTD module', function () { + describe('beforeInit', function () { + let submoduleStub; + + before(function () { + submoduleStub = sinon.stub(hook, 'submodule'); + }); + after(function () { + submoduleStub.restore(); + }); + it('should fetch the wrapper', function () { + beforeInit(); + let request = server.requests[0]; + let isWrapperRequest = request && request.url && request.url && request.url === WRAPPER_URL; + expect(isWrapperRequest).to.equal(true); + }); + it('should register RTD submodule provider', function () { + expect(submoduleStub.calledWith('realTimeData', geoedgeSubmodule)).to.equal(true); + }); + }); + describe('setWrapper', function () { + it('should set the wrapper', function () { + setWrapper(mockWrapper); + expect(wrapper).to.equal(mockWrapper); + }); + }); + describe('submodule', function () { + describe('name', function () { + it('should be geoedge', function () { + expect(geoedgeSubmodule.name).to.equal('geoedge'); + }); + }); + describe('init', function () { + let insertElementStub; + + before(function () { + insertElementStub = sinon.stub(utils, 'insertElement'); + }); + after(function () { + utils.insertElement.restore(); + }); + it('should return false when missing params or key', function () { + let missingParams = geoedgeSubmodule.init({}); + let missingKey = geoedgeSubmodule.init({ params: {} }); + expect(missingParams || missingKey).to.equal(false); + }); + it('should return true when params are ok', function () { + expect(geoedgeSubmodule.init(makeConfig())).to.equal(true); + }); + it('should preload the client', function () { + let isLinkPreloadAsScript = arg => arg.tagName === 'LINK' && arg.rel === 'preload' && arg.as === 'script' && arg.href === getClientUrl(key); + expect(insertElementStub.calledWith(sinon.match(isLinkPreloadAsScript))).to.equal(true); + }); + }); + describe('onBidResponseEvent', function () { + let bidFromA = mockBid('bidderA'); + it('should wrap bid html when bidder is configured', function () { + geoedgeSubmodule.onBidResponseEvent(bidFromA, makeConfig()); + expect(bidFromA.ad.indexOf('')).to.equal(0); + }); + it('should not wrap bid html when bidder is not configured', function () { + let bidFromB = mockBid('bidderB'); + geoedgeSubmodule.onBidResponseEvent(bidFromB, makeConfig()); + expect(bidFromB.ad.indexOf('')).to.equal(-1); + }); + it('should only muatate the bid ad porperty', function () { + let copy = Object.assign({}, bidFromA); + delete copy.ad; + let equalsOriginal = Object.keys(copy).every(key => copy[key] === bidFromA[key]); + expect(equalsOriginal).to.equal(true); + }); + }); + }); +}); From 678ffdfd69a666570dab4738c8b665b686a16458 Mon Sep 17 00:00:00 2001 From: wojciech-bialy-wpm <67895844+wojciech-bialy-wpm@users.noreply.github.com> Date: Wed, 25 Nov 2020 05:41:17 +0100 Subject: [PATCH 045/103] sspBC adapter: update to v4.6 (notifications, user sync) (#5941) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update adapter to v4.6 - add notification endpoint - send bidWon and onTimeout notifications - send CMP version to user sync endpoint Co-authored-by: Wojciech Biały --- modules/sspBCAdapter.js | 51 +++++++++++++++++-- test/spec/modules/sspBCAdapter_spec.js | 70 +++++++++++++++++++++++++- 2 files changed, 116 insertions(+), 5 deletions(-) diff --git a/modules/sspBCAdapter.js b/modules/sspBCAdapter.js index ef89fb08449..4069c722e9d 100644 --- a/modules/sspBCAdapter.js +++ b/modules/sspBCAdapter.js @@ -1,14 +1,17 @@ import * as utils from '../src/utils.js'; +import { ajax } from '../src/ajax.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; const BIDDER_CODE = 'sspBC'; 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 = '4.5'; +const BIDDER_VERSION = '4.6'; const W = window; const { navigator } = W; +var consentApiVersion; const cookieSupport = () => { const isSafari = /^((?!chrome|android|crios|fxios).)*safari/i.test(navigator.userAgent); @@ -53,6 +56,7 @@ const applyClientHints = ortbRequest => { function applyGdpr(bidderRequest, ortbRequest) { if (bidderRequest && bidderRequest.gdprConsent) { + consentApiVersion = bidderRequest.gdprConsent.apiVersion; ortbRequest.regs = Object.assign(ortbRequest.regs, { '[ortb_extensions.gdpr]': bidderRequest.gdprConsent.gdprApplies ? 1 : 0 }); ortbRequest.user = Object.assign(ortbRequest.user, { '[ortb_extensions.consent]': bidderRequest.gdprConsent.consentString }); } @@ -68,6 +72,14 @@ function setOnAny(collection, key) { } } +function sendNotification(payload) { + ajax(NOTIFY_URL, null, JSON.stringify(payload), { + withCredentials: false, + method: 'POST', + crossOrigin: true + }); +} + /** * @param {object} slot Ad Unit Params by Prebid * @returns {object} Banner by OpenRTB 2.5 §3.2.6 @@ -278,6 +290,7 @@ const spec = { mediaType: 'banner', meta: { advertiserDomains: serverBid.adomain, + networkName: seat, }, netRevenue: true, ad: renderCreative(site, response.id, serverBid, seat, request.bidderRequest), @@ -303,12 +316,44 @@ const spec = { if (syncOptions.iframeEnabled) { return [{ type: 'iframe', - url: SYNC_URL, + url: SYNC_URL + '?tcf=' + consentApiVersion, }]; } utils.logWarn('sspBC adapter requires iframe based user sync.'); }, - onTimeout() { + + onTimeout(timeoutData) { + var adSlots = []; + const bid = timeoutData && timeoutData[0]; + if (bid) { + timeoutData.forEach(bid => { adSlots.push(bid.params[0] && bid.params[0].id) }) + const payload = { + event: 'timeout', + requestId: bid.auctionId, + siteId: bid.params ? [bid.params[0].siteId] : [], + slotId: adSlots, + timeout: bid.timeout, + } + sendNotification(payload); + return payload; + } + }, + + onBidWon(bid) { + if (bid && bid.auctionId) { + const payload = { + event: 'bidWon', + requestId: bid.auctionId, + siteId: bid.params ? [bid.params[0].siteId] : [], + slotId: bid.params ? [bid.params[0].id] : [], + cpm: bid.cpm, + creativeId: bid.creativeId, + adomain: (bid.meta && bid.meta.advertiserDomains) ? bid.meta.advertiserDomains[0] : '', + networkName: (bid.meta && bid.meta.networkName) ? bid.meta.networkName : '', + } + sendNotification(payload); + return payload; + } }, }; diff --git a/test/spec/modules/sspBCAdapter_spec.js b/test/spec/modules/sspBCAdapter_spec.js index 2cb0e8defa4..29718deb031 100644 --- a/test/spec/modules/sspBCAdapter_spec.js +++ b/test/spec/modules/sspBCAdapter_spec.js @@ -1,6 +1,8 @@ import { assert, expect } from 'chai'; import { spec } from 'modules/sspBCAdapter.js'; import * as utils from 'src/utils.js'; +import * as sinon from 'sinon'; +import * as ajax from 'src/ajax.js'; const BIDDER_CODE = 'sspBC'; const BIDDER_URL = 'https://ssp.wp.pl/bidder/'; @@ -60,10 +62,33 @@ describe('SSPBC adapter', function () { }, auctionId, bidderRequestId, - bidId: auctionId + '1', + bidId: auctionId + '2', transactionId, } ]; + const bids_timeouted = [{ + adUnitCode: 'test_wideboard', + bidder: BIDDER_CODE, + params: [{ + id: '003', + siteId: '8816', + }], + auctionId, + bidId: auctionId + '1', + timeout: 100, + }, + { + adUnitCode: 'test_rectangle', + bidder: BIDDER_CODE, + params: [{ + id: '005', + siteId: '8816', + }], + auctionId, + bidId: auctionId + '2', + timeout: 100, + } + ]; const bids_test = [{ adUnitCode: 'test_wideboard', bidder: BIDDER_CODE, @@ -209,6 +234,7 @@ describe('SSPBC adapter', function () { return { bids, bids_test, + bids_timeouted, bidRequest, bidRequestSingle, bidRequestTest, @@ -323,7 +349,7 @@ describe('SSPBC adapter', function () { it('should provide correct url, if frame sync is allowed', function () { expect(syncResultAll).to.have.length(1); - expect(syncResultAll[0].url).to.be.equal(SYNC_URL); + expect(syncResultAll[0].url).to.have.string(SYNC_URL); }); it('should send no syncs, if frame sync is not allowed', function () { @@ -331,4 +357,44 @@ describe('SSPBC adapter', function () { expect(syncResultNone).to.be.undefined; }); }); + + describe('onBidWon', function () { + it('should generate no notification if bid is undefined', function () { + let notificationPayload = spec.onBidWon(); + expect(notificationPayload).to.be.undefined; + }); + + it('should generate notification with event name and request/site/slot data, if correct bid is provided', function () { + const { bids } = prepareTestData(); + let bid = bids[0]; + bid.params = [bid.params]; + + let notificationPayload = spec.onBidWon(bid); + expect(notificationPayload).to.have.property('event').that.equals('bidWon'); + expect(notificationPayload).to.have.property('requestId').that.equals(bid.auctionId); + expect(notificationPayload).to.have.property('siteId').that.deep.equals([bid.params[0].siteId]); + expect(notificationPayload).to.have.property('slotId').that.deep.equals([bid.params[0].id]); + }); + }); + + describe('onTimeout', function () { + it('should generate no notification if timeout data is undefined / has no bids', function () { + let notificationPayloadUndefined = spec.onTimeout(); + let notificationPayloadNoBids = spec.onTimeout([]); + + expect(notificationPayloadUndefined).to.be.undefined; + expect(notificationPayloadNoBids).to.be.undefined; + }); + + it('should generate single notification for any number of timeouted bids', function () { + const { bids_timeouted } = prepareTestData(); + + let notificationPayload = spec.onTimeout(bids_timeouted); + + expect(notificationPayload).to.have.property('event').that.equals('timeout'); + expect(notificationPayload).to.have.property('requestId').that.equals(bids_timeouted[0].auctionId); + expect(notificationPayload).to.have.property('siteId').that.deep.equals([bids_timeouted[0].params[0].siteId]); + expect(notificationPayload).to.have.property('slotId').that.deep.equals([bids_timeouted[0].params[0].id, bids_timeouted[1].params[0].id]); + }); + }); }); From 99fe1a77fa2b693517ecb139c7a40d0291c88e0b Mon Sep 17 00:00:00 2001 From: Neelanjan Sen <14229985+Fawke@users.noreply.github.com> Date: Wed, 25 Nov 2020 16:17:40 +0530 Subject: [PATCH 046/103] Appnexus - Send CriteoId in eids array. (#6025) * send criteo id in eids array * remove a line space --- modules/appnexusBidAdapter.js | 6 +++++- test/spec/modules/appnexusBidAdapter_spec.js | 5 +++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index ff0e3230007..f714472bbb1 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -229,16 +229,20 @@ export const spec = { } const criteoId = utils.deepAccess(bidRequests[0], `userId.criteoId`); + let eids = []; if (criteoId) { let tpuids = []; tpuids.push({ 'provider': 'criteo', 'user_id': criteoId }); + eids.push({ + source: 'criteo.com', + id: criteoId + }); payload.tpuids = tpuids; } - let eids = []; const tdid = utils.deepAccess(bidRequests[0], `userId.tdid`); if (tdid) { eids.push({ diff --git a/test/spec/modules/appnexusBidAdapter_spec.js b/test/spec/modules/appnexusBidAdapter_spec.js index 4102896ba94..21d3da358be 100644 --- a/test/spec/modules/appnexusBidAdapter_spec.js +++ b/test/spec/modules/appnexusBidAdapter_spec.js @@ -824,6 +824,11 @@ describe('AppNexusAdapter', function () { rti_partner: 'TDID' }); + expect(payload.eids).to.deep.include({ + source: 'criteo.com', + id: 'sample-criteo-userid', + }); + expect(payload.tpuids).to.deep.include({ provider: 'criteo', user_id: 'sample-criteo-userid', From e202cf5ad7ff39b0fa221908c8abdb31d2115847 Mon Sep 17 00:00:00 2001 From: Tucker <72400387+MenelikTucker-districtm@users.noreply.github.com> Date: Wed, 25 Nov 2020 08:21:39 -0500 Subject: [PATCH 047/103] allow users to be sent to dmx even when gdpr is configured in prebid (#6027) --- modules/districtmDMXBidAdapter.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/modules/districtmDMXBidAdapter.js b/modules/districtmDMXBidAdapter.js index a7bcead5f0b..845cff460ee 100644 --- a/modules/districtmDMXBidAdapter.js +++ b/modules/districtmDMXBidAdapter.js @@ -128,9 +128,12 @@ export const spec = { dmxRequest.regs = {}; dmxRequest.regs.ext = {}; dmxRequest.regs.ext.gdpr = bidderRequest.gdprConsent.gdprApplies === true ? 1 : 0; - dmxRequest.user = {}; - dmxRequest.user.ext = {}; - dmxRequest.user.ext.consent = bidderRequest.gdprConsent.consentString; + + if (bidderRequest.gdprConsent.gdprApplies === true) { + dmxRequest.user = {}; + dmxRequest.user.ext = {}; + dmxRequest.user.ext.consent = bidderRequest.gdprConsent.consentString; + } } dmxRequest.regs = dmxRequest.regs || {}; dmxRequest.regs.coppa = config.getConfig('coppa') === true ? 1 : 0; From 1137a64c508827d952a384975462b38d269cb46a Mon Sep 17 00:00:00 2001 From: Galphimbl Date: Wed, 25 Nov 2020 15:40:04 +0200 Subject: [PATCH 048/103] Admixer adapter update - add user syncs (#6024) * Migrating to Prebid 1.0 * Migrating to Prebid 1.0 * Fix spec * add gdpr and usp * remove changes in gdpr_hello_world.html * Update gdpr_hello_world.html add spaces * add user syncs * remove comments * tests Co-authored-by: atkachov --- modules/admixerBidAdapter.js | 20 ++++-- test/spec/modules/admixerBidAdapter_spec.js | 76 +++++++++++++++------ 2 files changed, 70 insertions(+), 26 deletions(-) diff --git a/modules/admixerBidAdapter.js b/modules/admixerBidAdapter.js index b2f24cfa910..3bb392538ff 100644 --- a/modules/admixerBidAdapter.js +++ b/modules/admixerBidAdapter.js @@ -3,7 +3,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'admixer'; const ALIASES = ['go2net']; -const ENDPOINT_URL = 'https://inv-nets.admixer.net/prebid.1.0.aspx'; +const ENDPOINT_URL = 'https://inv-nets.admixer.net/prebid.1.1.aspx'; export const spec = { code: BIDDER_CODE, aliases: ALIASES, @@ -51,10 +51,9 @@ export const spec = { */ interpretResponse: function (serverResponse, bidRequest) { const bidResponses = []; - // loop through serverResponses { try { - serverResponse = serverResponse.body; - serverResponse.forEach((bidResponse) => { + const {body: {ads = []} = {}} = serverResponse; + ads.forEach((bidResponse) => { const bidResp = { requestId: bidResponse.bidId, cpm: bidResponse.cpm, @@ -73,6 +72,19 @@ export const spec = { utils.logError(e); } return bidResponses; + }, + getUserSyncs: function(syncOptions, serverResponses, gdprConsent) { + const pixels = []; + serverResponses.forEach(({body: {cm = {}} = {}}) => { + const {pixels: img = [], iframes: frm = []} = cm; + if (syncOptions.pixelEnabled) { + img.forEach((url) => pixels.push({type: 'image', url})); + } + if (syncOptions.iframeEnabled) { + frm.forEach((url) => pixels.push({type: 'iframe', url})); + } + }); + return pixels; } }; registerBidder(spec); diff --git a/test/spec/modules/admixerBidAdapter_spec.js b/test/spec/modules/admixerBidAdapter_spec.js index 6d2e3059dc8..6298eac4448 100644 --- a/test/spec/modules/admixerBidAdapter_spec.js +++ b/test/spec/modules/admixerBidAdapter_spec.js @@ -3,7 +3,7 @@ import {spec} from 'modules/admixerBidAdapter.js'; import {newBidder} from 'src/adapters/bidderFactory.js'; const BIDDER_CODE = 'admixer'; -const ENDPOINT_URL = 'https://inv-nets.admixer.net/prebid.1.0.aspx'; +const ENDPOINT_URL = 'https://inv-nets.admixer.net/prebid.1.1.aspx'; const ZONE_ID = '2eb6bd58-865c-47ce-af7f-a918108c3fd2'; describe('AdmixerAdapter', function () { @@ -78,33 +78,35 @@ describe('AdmixerAdapter', function () { describe('interpretResponse', function () { let response = { - body: [{ - 'currency': 'USD', - 'cpm': 6.210000, - 'ad': '
ad
', - 'width': 300, - 'height': 600, - 'creativeId': 'ccca3e5e-0c54-4761-9667-771322fbdffc', - 'ttl': 360, - 'netRevenue': false, - 'bidId': '5e4e763b6bc60b' - }] + body: { + ads: [{ + 'currency': 'USD', + 'cpm': 6.210000, + 'ad': '
ad
', + 'width': 300, + 'height': 600, + 'creativeId': 'ccca3e5e-0c54-4761-9667-771322fbdffc', + 'ttl': 360, + 'netRevenue': false, + 'bidId': '5e4e763b6bc60b' + }] + } }; it('should get correct bid response', function () { - const body = response.body; + const ads = response.body.ads; let expectedResponse = [ { - 'requestId': body[0].bidId, - 'cpm': body[0].cpm, - 'creativeId': body[0].creativeId, - 'width': body[0].width, - 'height': body[0].height, - 'ad': body[0].ad, + 'requestId': ads[0].bidId, + 'cpm': ads[0].cpm, + 'creativeId': ads[0].creativeId, + 'width': ads[0].width, + 'height': ads[0].height, + 'ad': ads[0].ad, 'vastUrl': undefined, - 'currency': body[0].currency, - 'netRevenue': body[0].netRevenue, - 'ttl': body[0].ttl, + 'currency': ads[0].currency, + 'netRevenue': ads[0].netRevenue, + 'ttl': ads[0].ttl, } ]; @@ -119,4 +121,34 @@ describe('AdmixerAdapter', function () { expect(result.length).to.equal(0); }); }); + + describe('getUserSyncs', function () { + let imgUrl = 'https://example.com/img1'; + let frmUrl = 'https://example.com/frm2'; + let responses = [{ + body: { + cm: { + pixels: [ + imgUrl + ], + iframes: [ + frmUrl + ], + } + } + }]; + + it('Returns valid values', function () { + let userSyncAll = spec.getUserSyncs({pixelEnabled: true, iframeEnabled: true}, responses); + let userSyncImg = spec.getUserSyncs({pixelEnabled: true, iframeEnabled: false}, responses); + let userSyncFrm = spec.getUserSyncs({pixelEnabled: false, iframeEnabled: true}, responses); + expect(userSyncAll).to.be.an('array').with.lengthOf(2); + expect(userSyncImg).to.be.an('array').with.lengthOf(1); + expect(userSyncImg[0].url).to.be.equal(imgUrl); + expect(userSyncImg[0].type).to.be.equal('image'); + expect(userSyncFrm).to.be.an('array').with.lengthOf(1); + expect(userSyncFrm[0].url).to.be.equal(frmUrl); + expect(userSyncFrm[0].type).to.be.equal('iframe'); + }); + }); }); From 939c455a469703f0c98d021fd70d3b851ae69ba7 Mon Sep 17 00:00:00 2001 From: Olivier Date: Wed, 25 Nov 2020 16:41:39 +0100 Subject: [PATCH 049/103] Add support for `backupOnly` option in mediaType video renderer (#5972) * Add support for `backupOnly` option in mediaType video renderer * Improve readability --- src/Renderer.js | 18 +++++++++++++++++- src/auction.js | 6 +++--- test/spec/auctionmanager_spec.js | 32 ++++++++++++++++++++++++++++++++ test/spec/renderer_spec.js | 30 ++++++++++++++++++++++++++++++ 4 files changed, 82 insertions(+), 4 deletions(-) diff --git a/src/Renderer.js b/src/Renderer.js index f073d97d052..7cedf278537 100644 --- a/src/Renderer.js +++ b/src/Renderer.js @@ -115,5 +115,21 @@ function isRendererPreferredFromAdUnit(adUnitCode) { const adUnit = find(adUnits, adUnit => { return adUnit.code === adUnitCode; }); - return !!(adUnit && adUnit.renderer && adUnit.renderer.url && adUnit.renderer.render && !(utils.isBoolean(adUnit.renderer.backupOnly) && adUnit.renderer.backupOnly)); + + if (!adUnit) { + return false + } + + // renderer defined at adUnit level + const adUnitRenderer = utils.deepAccess(adUnit, 'renderer'); + const hasValidAdUnitRenderer = !!(adUnitRenderer && adUnitRenderer.url && adUnitRenderer.render); + + // renderer defined at adUnit.mediaTypes level + const mediaTypeRenderer = utils.deepAccess(adUnit, 'mediaTypes.video.renderer'); + const hasValidMediaTypeRenderer = !!(mediaTypeRenderer && mediaTypeRenderer.url && mediaTypeRenderer.render) + + return !!( + (hasValidAdUnitRenderer && !(adUnitRenderer.backupOnly === true)) || + (hasValidMediaTypeRenderer && !(mediaTypeRenderer.backupOnly === true)) + ); } diff --git a/src/auction.js b/src/auction.js index 6285bfdd905..c94e3adc9a7 100644 --- a/src/auction.js +++ b/src/auction.js @@ -57,7 +57,7 @@ * @property {function(): void} callBids - sends requests to all adapters for bids */ -import {flatten, timestamp, adUnitsFilter, deepAccess, getBidRequest, getValue, parseUrl, isBoolean} from './utils.js'; +import {flatten, timestamp, adUnitsFilter, deepAccess, getBidRequest, getValue, parseUrl} from './utils.js'; import { getPriceBucketString } from './cpmBucketManager.js'; import { getNativeTargeting } from './native.js'; import { getCacheUrl, store } from './videoCache.js'; @@ -533,9 +533,9 @@ function getPreparedBidForAuction({adUnitCode, bid, bidderRequest, auctionId}) { var renderer = null; // the renderer for the mediaType takes precendence - if (mediaTypeRenderer && mediaTypeRenderer.url && mediaTypeRenderer.render) { + if (mediaTypeRenderer && mediaTypeRenderer.url && !(mediaTypeRenderer.backupOnly === true && mediaTypeRenderer.render)) { renderer = mediaTypeRenderer; - } else if (adUnitRenderer && adUnitRenderer.url && !(adUnitRenderer.backupOnly && isBoolean(adUnitRenderer.backupOnly) && bid.renderer)) { + } else if (adUnitRenderer && adUnitRenderer.url && !(adUnitRenderer.backupOnly === true && bid.renderer)) { renderer = adUnitRenderer; } diff --git a/test/spec/auctionmanager_spec.js b/test/spec/auctionmanager_spec.js index d880ff0eaee..a50eba5e585 100644 --- a/test/spec/auctionmanager_spec.js +++ b/test/spec/auctionmanager_spec.js @@ -793,6 +793,38 @@ describe('auctionmanager.js', function () { assert.equal(addedBid.renderer.url, renderer.url); }); + it('installs bidder-defined renderer when onlyBackup is true in mediaTypes.video options ', function () { + const renderer = { + url: 'videoRenderer.js', + backupOnly: true, + render: (bid) => bid + }; + let myBid = mockBid(); + let bidRequest = mockBidRequest(myBid); + + bidRequest.bids[0] = { + ...bidRequest.bids[0], + mediaTypes: { + video: { + context: 'outstream', + renderer + } + } + }; + makeRequestsStub.returns([bidRequest]); + + myBid.mediaType = 'video'; + myBid.renderer = { + url: 'renderer.js', + render: sinon.spy() + }; + spec.interpretResponse.returns(myBid); + auction.callBids(); + + const addedBid = auction.getBidsReceived().pop(); + assert.strictEqual(addedBid.renderer.url, myBid.renderer.url); + }); + it('bid for a regular unit and a video unit', function() { let renderer = { url: 'renderer.js', diff --git a/test/spec/renderer_spec.js b/test/spec/renderer_spec.js index 9bf551f35e8..dcca94396cd 100644 --- a/test/spec/renderer_spec.js +++ b/test/spec/renderer_spec.js @@ -155,6 +155,36 @@ describe('Renderer', function () { expect(loadExternalScript.called).to.be.true; }); + it('should load external script instead of publisher-defined one when backupOnly option is true in mediaTypes.video options', function() { + $$PREBID_GLOBAL$$.adUnits = [{ + code: 'video1', + mediaTypes: { + video: { + context: 'outstream', + mimes: ['video/mp4'], + playerSize: [[400, 300]], + renderer: { + url: 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js', + backupOnly: true, + render: sinon.spy() + }, + } + } + }] + + let testRenderer = Renderer.install({ + url: 'https://httpbin.org/post', + config: { test: 'config1' }, + id: 1, + adUnitCode: 'video1' + + }); + testRenderer.setRender(() => {}) + + testRenderer.render() + expect(loadExternalScript.called).to.be.true; + }); + it('should call loadExternalScript() for script not defined on adUnit, only when .render() is called', function() { $$PREBID_GLOBAL$$.adUnits = [{ code: 'video1', From 18086e44fd33e921ef6ffe7ac8552d55c650fc35 Mon Sep 17 00:00:00 2001 From: mobfxoHB <74364234+mobfxoHB@users.noreply.github.com> Date: Mon, 30 Nov 2020 19:24:12 +0200 Subject: [PATCH 050/103] New mobfox prebid adapter (#5978) New mobfox prebid adapter --- modules/mobfoxpbBidAdapter.js | 99 ++++++ modules/mobfoxpbBidAdapter.md | 72 +++++ test/spec/modules/mobfoxpbBidAdapter_spec.js | 304 +++++++++++++++++++ 3 files changed, 475 insertions(+) create mode 100644 modules/mobfoxpbBidAdapter.js create mode 100644 modules/mobfoxpbBidAdapter.md create mode 100644 test/spec/modules/mobfoxpbBidAdapter_spec.js diff --git a/modules/mobfoxpbBidAdapter.js b/modules/mobfoxpbBidAdapter.js new file mode 100644 index 00000000000..c7e96b95179 --- /dev/null +++ b/modules/mobfoxpbBidAdapter.js @@ -0,0 +1,99 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import * as utils from '../src/utils.js'; + +const BIDDER_CODE = 'mobfoxpb'; +const AD_URL = 'https://bes.mobfox.com/?c=o&m=multi'; + +function isBidResponseValid(bid) { + if (!bid.requestId || !bid.cpm || !bid.creativeId || + !bid.ttl || !bid.currency) { + return false; + } + switch (bid.mediaType) { + case BANNER: + return Boolean(bid.width && bid.height && bid.ad); + case VIDEO: + return Boolean(bid.vastUrl); + case NATIVE: + return Boolean(bid.native && bid.native.impressionTrackers); + default: + return false; + } +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: (bid) => { + return Boolean(bid.bidId && bid.params && !isNaN(parseInt(bid.params.placementId))); + }, + + buildRequests: (validBidRequests = [], bidderRequest) => { + const winTop = utils.getWindowTop(); + const location = winTop.location; + const placements = []; + const request = { + 'deviceWidth': winTop.screen.width, + 'deviceHeight': winTop.screen.height, + 'language': (navigator && navigator.language) ? navigator.language.split('-')[0] : '', + 'secure': 1, + 'host': location.host, + 'page': location.pathname, + 'placements': placements + }; + + if (bidderRequest) { + if (bidderRequest.uspConsent) { + request.ccpa = bidderRequest.uspConsent; + } + if (bidderRequest.gdprConsent) { + request.gdpr = bidderRequest.gdprConsent + } + } + + const len = validBidRequests.length; + for (let i = 0; i < len; i++) { + const bid = validBidRequests[i]; + const placement = { + placementId: bid.params.placementId, + bidId: bid.bidId, + schain: bid.schain || {}, + }; + const mediaType = bid.mediaTypes + + if (mediaType && mediaType[BANNER] && mediaType[BANNER].sizes) { + placement.sizes = mediaType[BANNER].sizes; + placement.traffic = BANNER; + } else if (mediaType && mediaType[VIDEO] && mediaType[VIDEO].playerSize) { + placement.wPlayer = mediaType[VIDEO].playerSize[0]; + placement.hPlayer = mediaType[VIDEO].playerSize[1]; + placement.traffic = VIDEO; + } else if (mediaType && mediaType[NATIVE]) { + placement.native = mediaType[NATIVE]; + placement.traffic = NATIVE; + } + placements.push(placement); + } + + return { + method: 'POST', + url: AD_URL, + data: request + }; + }, + + interpretResponse: (serverResponse) => { + let response = []; + for (let i = 0; i < serverResponse.body.length; i++) { + let resItem = serverResponse.body[i]; + if (isBidResponseValid(resItem)) { + response.push(resItem); + } + } + return response; + }, +}; + +registerBidder(spec); diff --git a/modules/mobfoxpbBidAdapter.md b/modules/mobfoxpbBidAdapter.md new file mode 100644 index 00000000000..6eb549919d7 --- /dev/null +++ b/modules/mobfoxpbBidAdapter.md @@ -0,0 +1,72 @@ +# Overview + +``` +Module Name: mobfox Bidder Adapter +Module Type: mobfox Bidder Adapter +Maintainer: platform@mobfox.com +``` + +# Description + +Module that connects to mobfox demand sources + +# Test Parameters +``` + var adUnits = [ + { + code:'1', + mediaTypes:{ + banner: { + sizes: [[300, 250]], + } + }, + bids:[ + { + bidder: 'mobfoxpb', + params: { + placementId: 0 + } + } + ] + }, + { + code:'1', + mediaTypes:{ + video: { + playerSize: [640, 480], + context: 'instream' + } + }, + bids:[ + { + bidder: 'mobfoxpb', + params: { + placementId: 0 + } + } + ] + }, + { + code:'1', + mediaTypes:{ + native: { + title: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + }, + bids:[ + { + bidder: 'mobfoxpb', + params: { + placementId: 0 + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/test/spec/modules/mobfoxpbBidAdapter_spec.js b/test/spec/modules/mobfoxpbBidAdapter_spec.js new file mode 100644 index 00000000000..a02d580ab88 --- /dev/null +++ b/test/spec/modules/mobfoxpbBidAdapter_spec.js @@ -0,0 +1,304 @@ +import {expect} from 'chai'; +import {spec} from '../../../modules/mobfoxpbBidAdapter.js'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; + +describe('MobfoxHBBidAdapter', function () { + const bid = { + bidId: '23fhj33i987f', + bidder: 'mobfoxpb', + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 783, + traffic: BANNER + } + }; + + const bidderRequest = { + refererInfo: { + referer: 'test.com' + } + }; + + describe('isBidRequestValid', function () { + it('Should return true if there are bidId, params and key parameters present', function () { + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + it('Should return false if at least one of parameters is not present', function () { + delete bid.params.placementId; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + let serverRequest = spec.buildRequests([bid], 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://bes.mobfox.com/?c=o&m=multi'); + }); + 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', 'language', 'secure', 'host', 'page', 'placements'); + 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.gdpr).to.not.exist; + expect(data.ccpa).to.not.exist; + let placement = data['placements'][0]; + expect(placement).to.have.keys('placementId', 'bidId', 'traffic', 'sizes', 'schain'); + expect(placement.placementId).to.equal(783); + expect(placement.bidId).to.equal('23fhj33i987f'); + expect(placement.traffic).to.equal(BANNER); + expect(placement.schain).to.be.an('object'); + expect(placement.sizes).to.be.an('array'); + }); + + it('Returns valid data for mediatype video', function () { + const playerSize = [300, 300]; + bid.mediaTypes = {}; + bid.params.traffic = VIDEO; + bid.mediaTypes[VIDEO] = { + playerSize + }; + serverRequest = spec.buildRequests([bid], bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + let placement = data['placements'][0]; + expect(placement).to.be.an('object'); + expect(placement).to.have.keys('placementId', 'bidId', 'traffic', 'wPlayer', 'hPlayer', 'schain'); + expect(placement.traffic).to.equal(VIDEO); + expect(placement.wPlayer).to.equal(playerSize[0]); + expect(placement.hPlayer).to.equal(playerSize[1]); + }); + + it('Returns valid data for mediatype native', function () { + const native = { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + }; + + bid.mediaTypes = {}; + bid.params.traffic = NATIVE; + bid.mediaTypes[NATIVE] = native; + serverRequest = spec.buildRequests([bid], bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + let placement = data['placements'][0]; + expect(placement).to.be.an('object'); + expect(placement).to.have.keys('placementId', 'bidId', 'traffic', 'native', 'schain'); + expect(placement.traffic).to.equal(NATIVE); + expect(placement.native).to.equal(native); + }); + + it('Returns data with gdprConsent and without uspConsent', function () { + bidderRequest.gdprConsent = 'test'; + serverRequest = spec.buildRequests([bid], 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 = 'test'; + serverRequest = spec.buildRequests([bid], 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([]); + 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' + }] + }; + 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'); + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.4); + expect(dataItem.width).to.equal(300); + expect(dataItem.height).to.equal(250); + expect(dataItem.ad).to.equal('Test'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + }); + 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' + }] + }; + 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'); + 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'); + }); + 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', + }] + }; + 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'); + 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'); + }); + 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; + }); + }); +}); From 4b3faf46277eabc28c078f3bd94d2ad950de5261 Mon Sep 17 00:00:00 2001 From: Avimobi <34269094+Avimobi@users.noreply.github.com> Date: Tue, 1 Dec 2020 00:52:22 +0530 Subject: [PATCH 051/103] Update adkernelBidAdapter.js (#5957) * Update adkernelBidAdapter.js description: Prebid adbite Bidder Adaptor Host: cpm.adbite.com * Update adkernelBidAdapter_spec.js --- modules/adkernelBidAdapter.js | 2 +- test/spec/modules/adkernelBidAdapter_spec.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/adkernelBidAdapter.js b/modules/adkernelBidAdapter.js index 0e9093b0f63..29990ef1c44 100644 --- a/modules/adkernelBidAdapter.js +++ b/modules/adkernelBidAdapter.js @@ -52,7 +52,7 @@ const NATIVE_INDEX = NATIVE_MODEL.reduce((acc, val, idx) => { export const spec = { code: 'adkernel', - aliases: ['headbidding', 'adsolut', 'oftmediahb', 'audiencemedia', 'waardex_ak', 'roqoon', 'andbeyond'], + aliases: ['headbidding', 'adsolut', 'oftmediahb', 'audiencemedia', 'waardex_ak', 'roqoon', 'andbeyond', 'adbite'], supportedMediaTypes: [BANNER, VIDEO, NATIVE], /** diff --git a/test/spec/modules/adkernelBidAdapter_spec.js b/test/spec/modules/adkernelBidAdapter_spec.js index 4015a56e82b..70789c4b933 100644 --- a/test/spec/modules/adkernelBidAdapter_spec.js +++ b/test/spec/modules/adkernelBidAdapter_spec.js @@ -556,7 +556,7 @@ describe('Adkernel adapter', function () { describe('adapter configuration', () => { it('should have aliases', () => { - expect(spec.aliases).to.have.lengthOf(7); + expect(spec.aliases).to.have.lengthOf(8); }); }); From a7beb57e691fea813e036ffb507c4a4b89b43923 Mon Sep 17 00:00:00 2001 From: mmoschovas <63253416+mmoschovas@users.noreply.github.com> Date: Mon, 30 Nov 2020 15:34:43 -0500 Subject: [PATCH 052/103] RP Analytics Adapter update for UTM KVs (#5998) * RP Analytics Adapter update to scan URL query params to match 'utm_' params, convert to KV object format and pass data along as fpkvs. Unique query aram KVs will be added to the fpkvs object; whereas, query params will overwrite both matching KVs defined in localstorage or setConfig * Update to attempt to fix CircleCi errors * Update to use utils parseQS * Minor change to attempt to pass Safari tests * Switching spec logic to mock parseQS function as opposed to the window.location * Another attempt to determine Safari failure * Reverting last change and modifying fpkvs * Added sort to test to correctly evaluate on Safari browser and reverted back to windowLocation mock * Update to switch logic to utilize reduce() --- modules/rubiconAnalyticsAdapter.js | 22 +++++ .../modules/rubiconAnalyticsAdapter_spec.js | 87 +++++++++++++++++++ 2 files changed, 109 insertions(+) diff --git a/modules/rubiconAnalyticsAdapter.js b/modules/rubiconAnalyticsAdapter.js index 85b6596ba12..ff8cb7895b9 100644 --- a/modules/rubiconAnalyticsAdapter.js +++ b/modules/rubiconAnalyticsAdapter.js @@ -359,7 +359,29 @@ export function parseBidResponse(bid, previousBidResponse, auctionFloorData) { ]); } +/* + Filters and converts URL Params into an object and returns only KVs that match the 'utm_KEY' format +*/ +function getUtmParams() { + let search; + + try { + search = utils.parseQS(utils.getWindowLocation().search); + } catch (e) { + search = {}; + } + + return Object.keys(search).reduce((accum, param) => { + if (param.match(/utm_/)) { + accum[param.replace(/utm_/, '')] = search[param]; + } + return accum; + }, {}); +} + function getFpkvs() { + rubiConf.fpkvs = Object.assign((rubiConf.fpkvs || {}), getUtmParams()); + const isValid = rubiConf.fpkvs && typeof rubiConf.fpkvs === 'object' && Object.keys(rubiConf.fpkvs).every(key => typeof rubiConf.fpkvs[key] === 'string'); return isValid ? rubiConf.fpkvs : {}; } diff --git a/test/spec/modules/rubiconAnalyticsAdapter_spec.js b/test/spec/modules/rubiconAnalyticsAdapter_spec.js index 9e343d07dd5..4891b8d3282 100644 --- a/test/spec/modules/rubiconAnalyticsAdapter_spec.js +++ b/test/spec/modules/rubiconAnalyticsAdapter_spec.js @@ -1083,6 +1083,34 @@ describe('rubicon analytics adapter', function () { expect(message).to.deep.equal(expectedMessage); }); + it('should use the query utm param rubicon kv value and pass updated kv and pvid when defined', function () { + sandbox.stub(utils, 'getWindowLocation').returns({'search': '?utm_source=other', 'pbjs_debug': 'true'}); + + config.setConfig({rubicon: { + fpkvs: { + source: 'fb', + link: 'email' + } + }}); + performStandardAuction(); + expect(server.requests.length).to.equal(1); + let request = server.requests[0]; + let message = JSON.parse(request.requestBody); + validate(message); + + let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); + expectedMessage.session.pvid = STUBBED_UUID.slice(0, 8); + expectedMessage.fpkvs = [ + {key: 'source', value: 'other'}, + {key: 'link', value: 'email'} + ] + + message.fpkvs.sort((left, right) => left.key < right.key); + expectedMessage.fpkvs.sort((left, right) => left.key < right.key); + + expect(message).to.deep.equal(expectedMessage); + }); + it('should pick up existing localStorage and use its values', function () { // set some localStorage let inputlocalStorage = { @@ -1135,6 +1163,65 @@ describe('rubicon analytics adapter', function () { }); }); + it('should overwrite matching localstorge value and use its remaining values', function () { + sandbox.stub(utils, 'getWindowLocation').returns({'search': '?utm_source=fb&utm_click=dog'}); + + // set some localStorage + let inputlocalStorage = { + id: '987654', + start: 1519766113781, // 15 mins before "now" + expires: 1519787713781, // six hours later + lastSeen: 1519766113781, + fpkvs: { source: 'tw', link: 'email' } + }; + getDataFromLocalStorageStub.withArgs('rpaSession').returns(btoa(JSON.stringify(inputlocalStorage))); + + config.setConfig({rubicon: { + fpkvs: { + link: 'email' // should merge this with what is in the localStorage! + } + }}); + performStandardAuction(); + expect(server.requests.length).to.equal(1); + let request = server.requests[0]; + let message = JSON.parse(request.requestBody); + validate(message); + + let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); + expectedMessage.session = { + id: '987654', + start: 1519766113781, + expires: 1519787713781, + pvid: expectedPvid + } + expectedMessage.fpkvs = [ + {key: 'source', value: 'fb'}, + {key: 'link', value: 'email'}, + {key: 'click', value: 'dog'} + ] + + message.fpkvs.sort((left, right) => left.key < right.key); + expectedMessage.fpkvs.sort((left, right) => left.key < right.key); + + expect(message).to.deep.equal(expectedMessage); + + let calledWith; + try { + calledWith = JSON.parse(atob(setDataInLocalStorageStub.getCall(0).args[1])); + } catch (e) { + calledWith = {}; + } + + expect(calledWith).to.deep.equal({ + id: '987654', // should have stayed same + start: 1519766113781, // should have stayed same + expires: 1519787713781, // should have stayed same + lastSeen: 1519767013781, // lastSeen updated to our "now" + fpkvs: { source: 'fb', link: 'email', click: 'dog' }, // link merged in + pvid: expectedPvid // new pvid stored + }); + }); + it('should throw out session if lastSeen > 30 mins ago and create new one', function () { // set some localStorage let inputlocalStorage = { From 57ef24aef6ea7bce3f339843f12c606e079d160c Mon Sep 17 00:00:00 2001 From: Scott Menzer Date: Mon, 30 Nov 2020 21:38:29 +0100 Subject: [PATCH 053/103] remove pubcommon optout from user id module checks (#5994) --- modules/userId/index.js | 7 ++++--- test/spec/modules/userId_spec.js | 15 +++++---------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/modules/userId/index.js b/modules/userId/index.js index 0923a92f516..f063fbc973b 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -134,6 +134,7 @@ const CONSENT_DATA_COOKIE_STORAGE_CONFIG = { name: '_pbjs_userid_consent_data', expires: 30 // 30 days expiration, which should match how often consent is refreshed by CMPs }; +export const PBJS_USER_ID_OPTOUT_NAME = '_pbjs_id_optout'; export const coreStorage = getCoreStorageManager('userid'); /** @type {string[]} */ @@ -701,15 +702,15 @@ export function init(config) { ].filter(i => i !== null); // exit immediately if opt out cookie or local storage keys exists. - if (validStorageTypes.indexOf(COOKIE) !== -1 && (coreStorage.getCookie('_pbjs_id_optout') || coreStorage.getCookie('_pubcid_optout'))) { + if (validStorageTypes.indexOf(COOKIE) !== -1 && coreStorage.getCookie(PBJS_USER_ID_OPTOUT_NAME)) { utils.logInfo(`${MODULE_NAME} - opt-out cookie found, exit module`); return; } - // _pubcid_optout is checked for compatibility with pubCommonId - if (validStorageTypes.indexOf(LOCAL_STORAGE) !== -1 && (coreStorage.getDataFromLocalStorage('_pbjs_id_optout') || coreStorage.getDataFromLocalStorage('_pubcid_optout'))) { + if (validStorageTypes.indexOf(LOCAL_STORAGE) !== -1 && coreStorage.getDataFromLocalStorage(PBJS_USER_ID_OPTOUT_NAME)) { utils.logInfo(`${MODULE_NAME} - opt-out localStorage found, exit module`); return; } + // listen for config userSyncs to be set config.getConfig(conf => { // Note: support for 'usersync' was dropped as part of Prebid.js 4.0 diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index 887e1f45640..981ebb5f50e 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -7,7 +7,8 @@ import { setStoredConsentData, setStoredValue, setSubmoduleRegistry, - syncDelay + syncDelay, + PBJS_USER_ID_OPTOUT_NAME } from 'modules/userId/index.js'; import {createEidsArray} from 'modules/userId/eids.js'; import {config} from 'src/config.js'; @@ -91,9 +92,7 @@ describe('User ID', function () { } before(function () { - coreStorage.setCookie('_pubcid_optout', '', EXPIRED_COOKIE_DATE); - localStorage.removeItem('_pbjs_id_optout'); - localStorage.removeItem('_pubcid_optout'); + localStorage.removeItem(PBJS_USER_ID_OPTOUT_NAME); }); beforeEach(function () { @@ -413,7 +412,7 @@ describe('User ID', function () { describe('Opt out', function () { before(function () { - coreStorage.setCookie('_pbjs_id_optout', '1', (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie(PBJS_USER_ID_OPTOUT_NAME, '1', (new Date(Date.now() + 5000).toUTCString())); }); beforeEach(function () { @@ -422,16 +421,12 @@ describe('User ID', function () { afterEach(function () { // removed cookie - coreStorage.setCookie('_pbjs_id_optout', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie(PBJS_USER_ID_OPTOUT_NAME, '', EXPIRED_COOKIE_DATE); $$PREBID_GLOBAL$$.requestBids.removeAll(); utils.logInfo.restore(); config.resetConfig(); }); - after(function () { - coreStorage.setCookie('_pbjs_id_optout', '', EXPIRED_COOKIE_DATE); - }); - it('fails initialization if opt out cookie exists', function () { setSubmoduleRegistry([pubCommonIdSubmodule]); init(config); From 4638dffcb8996954dd0cd947c19c8eb56e614763 Mon Sep 17 00:00:00 2001 From: Scott Menzer Date: Mon, 30 Nov 2020 22:19:11 +0100 Subject: [PATCH 054/103] ID5 ID module - pass gdpr and usp parameters in body instead of querystring (#6032) * move gdpr and usp parameters to the body rather than the querystring of calls to ID5 * remove unnecessary variables --- modules/id5IdSystem.js | 7 ++++--- test/spec/modules/id5IdSystem_spec.js | 3 +++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/modules/id5IdSystem.js b/modules/id5IdSystem.js index a1596e96fcd..7033a71d015 100644 --- a/modules/id5IdSystem.js +++ b/modules/id5IdSystem.js @@ -78,13 +78,13 @@ export const id5IdSubmodule = { return undefined; } + const url = `https://id5-sync.com/g/v2/${config.params.partner}.json`; const hasGdpr = (consentData && typeof consentData.gdprApplies === 'boolean' && consentData.gdprApplies) ? 1 : 0; - const gdprConsentString = hasGdpr ? consentData.consentString : ''; - const usp = uspDataHandler.getConsentData() || ''; - const url = `https://id5-sync.com/g/v2/${config.params.partner}.json?gdpr_consent=${gdprConsentString}&gdpr=${hasGdpr}&us_privacy=${usp}`; const referer = getRefererInfo(); const signature = (cacheIdObj && cacheIdObj.signature) ? cacheIdObj.signature : getLegacyCookieSignature(); const data = { + 'gdpr': hasGdpr, + 'gdpr_consent': hasGdpr ? consentData.consentString : '', 'partner': config.params.partner, 'nbPage': incrementNb(config.params.partner), 'o': 'pbjs', @@ -94,6 +94,7 @@ export const id5IdSubmodule = { 's': signature, 'top': referer.reachedTop ? 1 : 0, 'u': referer.stack[0] || window.location.href, + 'us_privacy': uspDataHandler.getConsentData() || '', 'v': '$prebid.version$' }; diff --git a/test/spec/modules/id5IdSystem_spec.js b/test/spec/modules/id5IdSystem_spec.js index adffca6dbe5..845cf7fa010 100644 --- a/test/spec/modules/id5IdSystem_spec.js +++ b/test/spec/modules/id5IdSystem_spec.js @@ -143,6 +143,9 @@ describe('ID5 ID System', function() { expect(requestBody.s).to.eq(''); expect(requestBody.provider).to.eq(''); expect(requestBody.v).to.eq('$prebid.version$'); + expect(requestBody.gdpr).to.exist; + expect(requestBody.gdpr_consent).to.exist + expect(requestBody.us_privacy).to.exist; request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); expect(callbackSpy.calledOnce).to.be.true; From 6f147c6ab3e072b9b052ef390be757e1f9fb0383 Mon Sep 17 00:00:00 2001 From: Prebid Manager <49466873+Prebid-Manager@users.noreply.github.com> Date: Tue, 1 Dec 2020 03:51:20 +0000 Subject: [PATCH 055/103] Prebidmanager analytics adapter: fix console error when utm is null and collect page info (#6002) * Fix PrebidManager analytics console error when utm data is null * collect pageInfo in PrebidManager analytics adapter * minor edit + add test for pageInfo in PrebidManager analytics adapter Co-authored-by: apuzanova --- modules/prebidmanagerAnalyticsAdapter.js | 13 ++++++++++- .../prebidmanagerAnalyticsAdapter_spec.js | 23 ++++++++++++++++++- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/modules/prebidmanagerAnalyticsAdapter.js b/modules/prebidmanagerAnalyticsAdapter.js index b98ca864cd5..994ce4989f5 100644 --- a/modules/prebidmanagerAnalyticsAdapter.js +++ b/modules/prebidmanagerAnalyticsAdapter.js @@ -83,7 +83,7 @@ function collectUtmTagData() { if (newUtm === false) { utmTags.forEach(function (utmKey) { let itemValue = localStorage.getItem(`pm_${utmKey}`); - if (itemValue.length !== 0) { + if (itemValue && itemValue.length !== 0) { pmUtmTags[utmKey] = itemValue; } }); @@ -99,6 +99,16 @@ function collectUtmTagData() { return pmUtmTags; } +function collectPageInfo() { + const pageInfo = { + domain: window.location.hostname, + } + if (document.referrer) { + pageInfo.referrerDomain = utils.parseUrl(document.referrer).hostname; + } + return pageInfo; +} + function flush() { if (!pmAnalyticsEnabled) { return; @@ -111,6 +121,7 @@ function flush() { bundleId: initOptions.bundleId, events: _eventQueue, utmTags: collectUtmTagData(), + pageInfo: collectPageInfo(), }; ajax( diff --git a/test/spec/modules/prebidmanagerAnalyticsAdapter_spec.js b/test/spec/modules/prebidmanagerAnalyticsAdapter_spec.js index ce97789fe3e..ef7cb2bbe3b 100644 --- a/test/spec/modules/prebidmanagerAnalyticsAdapter_spec.js +++ b/test/spec/modules/prebidmanagerAnalyticsAdapter_spec.js @@ -1,6 +1,8 @@ import prebidmanagerAnalytics from 'modules/prebidmanagerAnalyticsAdapter.js'; import {expect} from 'chai'; import {server} from 'test/mocks/xhr.js'; +import * as utils from 'src/utils.js'; + let events = require('src/events'); let constants = require('src/constants.json'); @@ -98,7 +100,7 @@ describe('Prebid Manager Analytics Adapter', function () { events.emit(constants.EVENTS.AUCTION_END, {}); events.emit(constants.EVENTS.BID_TIMEOUT, {}); - sinon.assert.callCount(prebidmanagerAnalytics.track, 7); + sinon.assert.callCount(prebidmanagerAnalytics.track, 6); }); }); @@ -135,4 +137,23 @@ describe('Prebid Manager Analytics Adapter', function () { expect(pmEvents.utmTags.utm_content).to.equal(''); }); }); + + describe('build page info', function () { + afterEach(function () { + prebidmanagerAnalytics.disableAnalytics() + }); + it('should build page info', function () { + prebidmanagerAnalytics.enableAnalytics({ + provider: 'prebidmanager', + options: { + bundleId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' + } + }); + + const pmEvents = JSON.parse(server.requests[0].requestBody.substring(2)); + + expect(pmEvents.pageInfo.domain).to.equal(window.location.hostname); + expect(pmEvents.pageInfo.referrerDomain).to.equal(utils.parseUrl(document.referrer).hostname); + }); + }); }); From d5f228bd312d657e1f4881ebb51114696846bbf4 Mon Sep 17 00:00:00 2001 From: Nicholas Llerandi Date: Mon, 30 Nov 2020 22:56:03 -0500 Subject: [PATCH 056/103] Rp adapter unit tests - userid mod support (#5985) * ID5 support * ID5 support; tests passed * no-console:error * UserId catchall support * minor revision --- test/spec/modules/rubiconBidAdapter_spec.js | 37 +++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index 6659c281c33..b1ef02a6369 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -1221,6 +1221,43 @@ describe('the rubicon adapter', function () { }); }); + describe('ID5 support', function () { + it('should send ID5 id when userIdAsEids contains ID5', function () { + const clonedBid = utils.deepClone(bidderRequest.bids[0]); + clonedBid.userId = { + id5id: { + uid: '11111', + ext: { + linkType: '22222' + } + } + }; + clonedBid.userIdAsEids = createEidsArray(clonedBid.userId); + let [request] = spec.buildRequests([clonedBid], bidderRequest); + let data = parseQuery(request.data); + + expect(data['eid_id5-sync.com']).to.equal('11111^1^22222'); + }); + }); + + describe('UserID catchall support', function () { + it('should send user id with generic format', function () { + const clonedBid = utils.deepClone(bidderRequest.bids[0]); + // Hardcoding userIdAsEids since createEidsArray returns empty array if source not found in eids.js + clonedBid.userIdAsEids = [{ + source: 'catchall', + uids: [{ + id: '11111', + atype: 2 + }] + }] + let [request] = spec.buildRequests([clonedBid], bidderRequest); + let data = parseQuery(request.data); + + expect(data['eid_catchall']).to.equal('11111^2'); + }); + }); + describe('Config user.id support', function () { it('should send ppuid when config defines user.id', function () { config.setConfig({user: {id: '123'}}); From 0820790bbc1509b9c964f15d1487b5cff990cef2 Mon Sep 17 00:00:00 2001 From: bjorn-lw <32431346+bjorn-lw@users.noreply.github.com> Date: Tue, 1 Dec 2020 11:33:33 +0100 Subject: [PATCH 057/103] Read floor data in analytic + support for Criteo Id (#6003) * Livewrapped bid and analytics adapter * Fixed some tests for browser compatibility * Fixed some tests for browser compatibility * Changed analytics adapter code name * Fix double quote in debug message * modified how gdpr is being passed * Added support for Publisher Common ID Module * Corrections for ttr in analytics * ANalytics updates * Auction start time stamp changed * Detect recovered ad blocked requests Make it possible to pass dynamic parameters to adapter * Collect info on ad units receiving any valid bid * Support for ID5 Pass metadata from adapter * Typo in test + eids on wrong level * Fix for Prebid 3.0 * Fix get referer * http -> https in tests * Native support * Read sizes from mediatype.banner * Revert accidental commit * Support native data collection + minor refactorings * Set analytics endpoint * Support for app parameters * Fix issue where adunits with bids were not counted on reload * Send debug info from adapter to external debugger * SChain support * Send GDPR data in analytics request * video support Video support * Report back floor via analytic * Send auction id and adunit/bidder connection id * Criteo id support * Updated example * livewrapped Analytics Adapter info file --- modules/livewrappedAnalyticsAdapter.js | 108 +++++++++++--- modules/livewrappedAnalyticsAdapter.md | 22 +++ modules/livewrappedBidAdapter.js | 7 +- modules/livewrappedBidAdapter.md | 2 +- .../livewrappedAnalyticsAdapter_spec.js | 132 ++++++++++++++++-- .../modules/livewrappedBidAdapter_spec.js | 21 ++- 6 files changed, 257 insertions(+), 35 deletions(-) create mode 100644 modules/livewrappedAnalyticsAdapter.md diff --git a/modules/livewrappedAnalyticsAdapter.js b/modules/livewrappedAnalyticsAdapter.js index 9f571cb5ae0..a872a709ec9 100644 --- a/modules/livewrappedAnalyticsAdapter.js +++ b/modules/livewrappedAnalyticsAdapter.js @@ -36,6 +36,15 @@ let livewrappedAnalyticsAdapter = Object.assign(adapter({EMPTYURL, ANALYTICSTYPE args.bids.forEach(function(bidRequest) { cache.auctions[args.auctionId].gdprApplies = args.gdprConsent ? args.gdprConsent.gdprApplies : undefined; cache.auctions[args.auctionId].gdprConsent = args.gdprConsent ? args.gdprConsent.consentString : undefined; + let lwFloor; + + if (bidRequest.lwflr) { + lwFloor = bidRequest.lwflr.flr; + + let buyerFloor = bidRequest.lwflr.bflrs ? bidRequest.lwflr.bflrs[bidRequest.bidder] : undefined; + + lwFloor = buyerFloor || lwFloor; + } cache.auctions[args.auctionId].bids[bidRequest.bidId] = { bidder: bidRequest.bidder, @@ -45,7 +54,11 @@ let livewrappedAnalyticsAdapter = Object.assign(adapter({EMPTYURL, ANALYTICSTYPE timeout: false, sendStatus: 0, readyToSend: 0, - start: args.start + start: args.start, + lwFloor: lwFloor, + floorData: bidRequest.floorData, + auc: bidRequest.auc, + buc: bidRequest.buc } utils.logInfo(bidRequest); @@ -123,10 +136,11 @@ livewrappedAnalyticsAdapter.sendEvents = function() { var events = { publisherId: initOptions.publisherId, gdpr: sentRequests.gdpr, + auctionIds: sentRequests.auctionIds, requests: sentRequests.sentRequests, - responses: getResponses(), - wins: getWins(), - timeouts: getTimeouts(), + responses: getResponses(sentRequests.gdpr, sentRequests.auctionIds), + wins: getWins(sentRequests.gdpr, sentRequests.auctionIds), + timeouts: getTimeouts(sentRequests.auctionIds), bidAdUnits: getbidAdUnits(), rcv: getAdblockerRecovered() }; @@ -150,20 +164,12 @@ function getAdblockerRecovered() { function getSentRequests() { var sentRequests = []; var gdpr = []; + var auctionIds = []; Object.keys(cache.auctions).forEach(auctionId => { let auction = cache.auctions[auctionId]; - var gdprPos = 0; - for (gdprPos = 0; gdprPos < gdpr.length; gdprPos++) { - if (gdpr[gdprPos].gdprApplies == auction.gdprApplies && - gdpr[gdprPos].gdprConsent == auction.gdprConsent) { - break; - } - } - - if (gdprPos == gdpr.length) { - gdpr[gdprPos] = {gdprApplies: auction.gdprApplies, gdprConsent: auction.gdprConsent}; - } + let gdprPos = getGdprPos(gdpr, auction); + let auctionIdPos = getAuctionIdPos(auctionIds, auctionId); Object.keys(cache.auctions[auctionId].bids).forEach(bidId => { let bid = auction.bids[bidId]; @@ -174,21 +180,27 @@ function getSentRequests() { timeStamp: auction.timeStamp, adUnit: bid.adUnit, bidder: bid.bidder, - gdpr: gdprPos + gdpr: gdprPos, + floor: bid.lwFloor, + auctionId: auctionIdPos, + auc: bid.auc, + buc: bid.buc }); } }); }); - return {gdpr: gdpr, sentRequests: sentRequests}; + return {gdpr: gdpr, auctionIds: auctionIds, sentRequests: sentRequests}; } -function getResponses() { +function getResponses(gdpr, auctionIds) { var responses = []; Object.keys(cache.auctions).forEach(auctionId => { Object.keys(cache.auctions[auctionId].bids).forEach(bidId => { let auction = cache.auctions[auctionId]; + let gdprPos = getGdprPos(gdpr, auction); + let auctionIdPos = getAuctionIdPos(auctionIds, auctionId) let bid = auction.bids[bidId]; if (bid.readyToSend && !(bid.sendStatus & RESPONSESENT) && !bid.timeout) { bid.sendStatus |= RESPONSESENT; @@ -202,7 +214,13 @@ function getResponses() { cpm: bid.cpm, ttr: bid.ttr, IsBid: bid.isBid, - mediaType: bid.mediaType + mediaType: bid.mediaType, + gdpr: gdprPos, + floor: bid.floorData ? bid.floorData.floorValue : bid.lwFloor, + floorCur: bid.floorData ? bid.floorData.floorCurrency : undefined, + auctionId: auctionIdPos, + auc: bid.auc, + buc: bid.buc }); } }); @@ -211,13 +229,16 @@ function getResponses() { return responses; } -function getWins() { +function getWins(gdpr, auctionIds) { var wins = []; Object.keys(cache.auctions).forEach(auctionId => { Object.keys(cache.auctions[auctionId].bids).forEach(bidId => { let auction = cache.auctions[auctionId]; + let gdprPos = getGdprPos(gdpr, auction); + let auctionIdPos = getAuctionIdPos(auctionIds, auctionId); let bid = auction.bids[bidId]; + if (!(bid.sendStatus & WINSENT) && bid.won) { bid.sendStatus |= WINSENT; @@ -228,7 +249,13 @@ function getWins() { width: bid.width, height: bid.height, cpm: bid.cpm, - mediaType: bid.mediaType + mediaType: bid.mediaType, + gdpr: gdprPos, + floor: bid.floorData ? bid.floorData.floorValue : bid.lwFloor, + floorCur: bid.floorData ? bid.floorData.floorCurrency : undefined, + auctionId: auctionIdPos, + auc: bid.auc, + buc: bid.buc }); } }); @@ -237,10 +264,42 @@ function getWins() { return wins; } -function getTimeouts() { +function getGdprPos(gdpr, auction) { + var gdprPos = 0; + for (gdprPos = 0; gdprPos < gdpr.length; gdprPos++) { + if (gdpr[gdprPos].gdprApplies == auction.gdprApplies && + gdpr[gdprPos].gdprConsent == auction.gdprConsent) { + break; + } + } + + if (gdprPos == gdpr.length) { + gdpr[gdprPos] = {gdprApplies: auction.gdprApplies, gdprConsent: auction.gdprConsent}; + } + + return gdprPos; +} + +function getAuctionIdPos(auctionIds, auctionId) { + var auctionIdPos = 0; + for (auctionIdPos = 0; auctionIdPos < auctionIds.length; auctionIdPos++) { + if (auctionIds[auctionIdPos] == auctionId) { + break; + } + } + + if (auctionIdPos == auctionIds.length) { + auctionIds[auctionIdPos] = auctionId; + } + + return auctionIdPos; +} + +function getTimeouts(auctionIds) { var timeouts = []; Object.keys(cache.auctions).forEach(auctionId => { + let auctionIdPos = getAuctionIdPos(auctionIds, auctionId); Object.keys(cache.auctions[auctionId].bids).forEach(bidId => { let auction = cache.auctions[auctionId]; let bid = auction.bids[bidId]; @@ -250,7 +309,10 @@ function getTimeouts() { timeouts.push({ bidder: bid.bidder, adUnit: bid.adUnit, - timeStamp: auction.timeStamp + timeStamp: auction.timeStamp, + auctionId: auctionIdPos, + auc: bid.auc, + buc: bid.buc }); } }); diff --git a/modules/livewrappedAnalyticsAdapter.md b/modules/livewrappedAnalyticsAdapter.md new file mode 100644 index 00000000000..de4f352aa19 --- /dev/null +++ b/modules/livewrappedAnalyticsAdapter.md @@ -0,0 +1,22 @@ +# Overview +Module Name: Livewrapped Analytics Adapter + +Module Type: Analytics Adapter + +Maintainer: info@livewrapped.com + +# Description + +Analytics adapter for Livewrapped AB. In order to use the adapter, please contact Livewrapped AB. + +# Test Parameters + +``` +{ + provider: 'livewrapped', + options : { + publisherId: "64c01620-fa98-4936-9794-6001d8ebfdb0" + } +} + +``` diff --git a/modules/livewrappedBidAdapter.js b/modules/livewrappedBidAdapter.js index 0a5464fd21f..512fc775d95 100644 --- a/modules/livewrappedBidAdapter.js +++ b/modules/livewrappedBidAdapter.js @@ -221,6 +221,10 @@ function bidToAdRequest(bid) { options: bid.params.options }; + if (bid.auc !== undefined) { + adRequest.auc = bid.auc; + } + adRequest.native = utils.deepAccess(bid, 'mediaTypes.native'); adRequest.video = utils.deepAccess(bid, 'mediaTypes.video'); @@ -276,8 +280,9 @@ function handleEids(bidRequests) { let eids = []; const bidRequest = bidRequests[0]; if (bidRequest && bidRequest.userId) { - AddExternalUserId(eids, utils.deepAccess(bidRequest, `userId.pubcid`), 'pubcommon', 1); // Also add this to eids + AddExternalUserId(eids, utils.deepAccess(bidRequest, `userId.pubcid`), 'pubcid.org', 1); // Also add this to eids AddExternalUserId(eids, utils.deepAccess(bidRequest, `userId.id5id.uid`), 'id5-sync.com', 1); + AddExternalUserId(eids, utils.deepAccess(bidRequest, `userId.criteoId`), 'criteo.com', 1); } if (eids.length > 0) { return {user: {ext: {eids}}}; diff --git a/modules/livewrappedBidAdapter.md b/modules/livewrappedBidAdapter.md index c5d867af8fe..10fc2a4778a 100644 --- a/modules/livewrappedBidAdapter.md +++ b/modules/livewrappedBidAdapter.md @@ -20,7 +20,7 @@ var adUnits = [ bids: [{ bidder: 'livewrapped', params: { - adUnitId: '6A32352E-BC17-4B94-B2A7-5BF1724417D7' + adUnitId: 'D801852A-681F-11E8-86A7-0A44794250D4' } }] } diff --git a/test/spec/modules/livewrappedAnalyticsAdapter_spec.js b/test/spec/modules/livewrappedAnalyticsAdapter_spec.js index c723f589fa0..ba9430e0b95 100644 --- a/test/spec/modules/livewrappedAnalyticsAdapter_spec.js +++ b/test/spec/modules/livewrappedAnalyticsAdapter_spec.js @@ -121,6 +121,7 @@ const MOCK = { const ANALYTICS_MESSAGE = { publisherId: 'CC411485-42BC-4F92-8389-42C503EE38D7', gdpr: [{}], + auctionIds: ['25c6d7f5-699a-4bfc-87c9-996f915341fa'], bidAdUnits: [ { adUnit: 'panorama_d_1', @@ -136,19 +137,22 @@ const ANALYTICS_MESSAGE = { adUnit: 'panorama_d_1', bidder: 'livewrapped', timeStamp: 1519149562216, - gdpr: 0 + gdpr: 0, + auctionId: 0 }, { adUnit: 'box_d_1', bidder: 'livewrapped', timeStamp: 1519149562216, - gdpr: 0 + gdpr: 0, + auctionId: 0 }, { adUnit: 'box_d_2', bidder: 'livewrapped', timeStamp: 1519149562216, - gdpr: 0 + gdpr: 0, + auctionId: 0 } ], responses: [ @@ -161,7 +165,9 @@ const ANALYTICS_MESSAGE = { cpm: 1.1, ttr: 200, IsBid: true, - mediaType: 1 + mediaType: 1, + gdpr: 0, + auctionId: 0 }, { timeStamp: 1519149562216, @@ -172,14 +178,18 @@ const ANALYTICS_MESSAGE = { cpm: 2.2, ttr: 300, IsBid: true, - mediaType: 1 + mediaType: 1, + gdpr: 0, + auctionId: 0 }, { timeStamp: 1519149562216, adUnit: 'box_d_2', bidder: 'livewrapped', ttr: 200, - IsBid: false + IsBid: false, + gdpr: 0, + auctionId: 0 } ], timeouts: [], @@ -191,7 +201,9 @@ const ANALYTICS_MESSAGE = { width: 980, height: 240, cpm: 1.1, - mediaType: 1 + mediaType: 1, + gdpr: 0, + auctionId: 0 }, { timeStamp: 1519149562216, @@ -200,7 +212,9 @@ const ANALYTICS_MESSAGE = { width: 300, height: 250, cpm: 2.2, - mediaType: 1 + mediaType: 1, + gdpr: 0, + auctionId: 0 } ] }; @@ -351,7 +365,9 @@ describe('Livewrapped analytics adapter', function () { } }, ); - events.emit(BID_TIMEOUT, MOCK.BID_TIMEOUT); + + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); + events.emit(BID_WON, MOCK.BID_WON[0]); events.emit(AUCTION_END, MOCK.AUCTION_END); clock.tick(BID_WON_TIMEOUT + 1000); @@ -366,6 +382,104 @@ describe('Livewrapped analytics adapter', function () { expect(message.requests.length).to.equal(2); expect(message.requests[0].gdpr).to.equal(0); expect(message.requests[1].gdpr).to.equal(0); + + expect(message.responses.length).to.equal(1); + expect(message.responses[0].gdpr).to.equal(0); + + expect(message.wins.length).to.equal(1); + expect(message.wins[0].gdpr).to.equal(0); + }); + + it('should forward floor data', function () { + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, { + 'bidder': 'livewrapped', + 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', + 'bidderRequestId': '1be65d7958826a', + 'bids': [ + { + 'bidder': 'livewrapped', + 'adUnitCode': 'panorama_d_1', + 'bidId': '2ecff0db240757', + 'floorData': { + 'floorValue': 1.1, + 'floorCurrency': 'SEK' + } + } + ], + 'start': 1519149562216 + }); + + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); + events.emit(BID_WON, MOCK.BID_WON[0]); + 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.gdpr.length).to.equal(1); + + expect(message.responses.length).to.equal(1); + expect(message.responses[0].floor).to.equal(1.1); + expect(message.responses[0].floorCur).to.equal('SEK'); + + expect(message.wins.length).to.equal(1); + expect(message.wins[0].floor).to.equal(1.1); + expect(message.wins[0].floorCur).to.equal('SEK'); + }); + + it('should forward Livewrapped floor data', function () { + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, { + 'bidder': 'livewrapped', + 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', + 'bidderRequestId': '1be65d7958826a', + 'bids': [ + { + 'bidder': 'livewrapped', + 'adUnitCode': 'panorama_d_1', + 'bidId': '2ecff0db240757', + 'lwflr': { + 'flr': 1.1 + } + }, + { + 'bidder': 'livewrapped', + 'adUnitCode': 'box_d_1', + 'bidId': '3ecff0db240757', + 'lwflr': { + 'flr': 1.1, + 'bflrs': {'livewrapped': 2.2} + } + } + ], + 'start': 1519149562216 + }); + + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[1]); + events.emit(BID_WON, MOCK.BID_WON[0]); + events.emit(BID_WON, MOCK.BID_WON[1]); + 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.gdpr.length).to.equal(1); + + expect(message.responses.length).to.equal(2); + expect(message.responses[0].floor).to.equal(1.1); + expect(message.responses[1].floor).to.equal(2.2); + + expect(message.wins.length).to.equal(2); + expect(message.wins[0].floor).to.equal(1.1); + expect(message.wins[1].floor).to.equal(2.2); }); }); diff --git a/test/spec/modules/livewrappedBidAdapter_spec.js b/test/spec/modules/livewrappedBidAdapter_spec.js index 2d5ba3f48df..7983e8fbb0b 100644 --- a/test/spec/modules/livewrappedBidAdapter_spec.js +++ b/test/spec/modules/livewrappedBidAdapter_spec.js @@ -811,7 +811,7 @@ describe('Livewrapped adapter tests', function () { let data = JSON.parse(result.data); expect(data.rtbData.user.ext.eids).to.deep.equal([{ - 'source': 'pubcommon', + 'source': 'pubcid.org', 'uids': [{ 'id': 'publisher-common-id', 'atype': 1 @@ -819,6 +819,25 @@ describe('Livewrapped adapter tests', function () { }]); }); + it('should make use of criteoId if available', function() { + sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); + sandbox.stub(storage, 'cookiesAreEnabled').callsFake(() => true); + let testbidRequest = clone(bidderRequest); + delete testbidRequest.bids[0].params.userId; + testbidRequest.bids[0].userId = {}; + testbidRequest.bids[0].userId.criteoId = 'criteo-id'; + let result = spec.buildRequests(testbidRequest.bids, testbidRequest); + let data = JSON.parse(result.data); + + expect(data.rtbData.user.ext.eids).to.deep.equal([{ + 'source': 'criteo.com', + 'uids': [{ + 'id': 'criteo-id', + 'atype': 1 + }] + }]); + }); + it('should send schain object if available', function() { sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); sandbox.stub(storage, 'cookiesAreEnabled').callsFake(() => true); From 24cae189046b52d753ce5720a1701bd3ed7125f1 Mon Sep 17 00:00:00 2001 From: Galphimbl Date: Tue, 1 Dec 2020 16:43:31 +0200 Subject: [PATCH 058/103] Admixer adapter update - add deal id (#6042) * Migrating to Prebid 1.0 * Migrating to Prebid 1.0 * Fix spec * add gdpr and usp * remove changes in gdpr_hello_world.html * Update gdpr_hello_world.html add spaces * add user syncs * remove comments * tests * add-deal-id Co-authored-by: atkachov --- modules/admixerBidAdapter.js | 1 + test/spec/modules/admixerBidAdapter_spec.js | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/admixerBidAdapter.js b/modules/admixerBidAdapter.js index 3bb392538ff..405dd81cc8c 100644 --- a/modules/admixerBidAdapter.js +++ b/modules/admixerBidAdapter.js @@ -65,6 +65,7 @@ export const spec = { netRevenue: bidResponse.netRevenue, currency: bidResponse.currency, vastUrl: bidResponse.vastUrl, + dealId: bidResponse.dealId, }; bidResponses.push(bidResp); }); diff --git a/test/spec/modules/admixerBidAdapter_spec.js b/test/spec/modules/admixerBidAdapter_spec.js index 6298eac4448..dfadf1f95d5 100644 --- a/test/spec/modules/admixerBidAdapter_spec.js +++ b/test/spec/modules/admixerBidAdapter_spec.js @@ -88,7 +88,8 @@ describe('AdmixerAdapter', function () { 'creativeId': 'ccca3e5e-0c54-4761-9667-771322fbdffc', 'ttl': 360, 'netRevenue': false, - 'bidId': '5e4e763b6bc60b' + 'bidId': '5e4e763b6bc60b', + 'dealId': 'asd123', }] } }; @@ -107,6 +108,7 @@ describe('AdmixerAdapter', function () { 'currency': ads[0].currency, 'netRevenue': ads[0].netRevenue, 'ttl': ads[0].ttl, + 'dealId': ads[0].dealId, } ]; From 2609a1b804394fadd05bb99cd3c314e326f5796a Mon Sep 17 00:00:00 2001 From: Vladimir Fedoseev Date: Tue, 1 Dec 2020 16:03:51 +0100 Subject: [PATCH 059/103] FID-251: Update Reconciliation RTD Provider to 2.1 (#6037) --- .../reconciliationRtdProvider_example.html | 5 +- modules/reconciliationRtdProvider.js | 27 +------- .../modules/reconciliationRtdProvider_spec.js | 66 ++++++++----------- 3 files changed, 34 insertions(+), 64 deletions(-) diff --git a/integrationExamples/gpt/reconciliationRtdProvider_example.html b/integrationExamples/gpt/reconciliationRtdProvider_example.html index af414e0b055..4f9b663c22d 100644 --- a/integrationExamples/gpt/reconciliationRtdProvider_example.html +++ b/integrationExamples/gpt/reconciliationRtdProvider_example.html @@ -82,8 +82,9 @@ `); adSlotIframe.contentDocument.close(); setTimeout(() => { - expect(trackGetStub.calledOnce).to.be.true; - expect(trackGetStub.getCalls()[0].args[0]).to.eql('https://confirm.fiduciadlt.com/imp'); - expect(trackGetStub.getCalls()[0].args[1].adUnitId).to.eql('/reconciliationAdunit'); - expect(trackGetStub.getCalls()[0].args[1].adDeliveryId).to.eql('12345'); - expect(trackGetStub.getCalls()[0].args[1].sourceMemberId).to.eql('test_member_id'); ; - expect(trackGetStub.getCalls()[0].args[1].sourceImpressionId).to.eql('123'); ; - expect(trackGetStub.getCalls()[0].args[1].publisherMemberId).to.eql('test_prebid_publisher'); + expect(trackPostStub.calledOnce).to.be.true; + expect(trackPostStub.getCalls()[0].args[0]).to.eql('https://confirm.fiduciadlt.com/pimp'); + expect(trackPostStub.getCalls()[0].args[1].adUnitId).to.eql('/reconciliationAdunit'); + expect(trackPostStub.getCalls()[0].args[1].adDeliveryId).to.eql('12345'); + expect(trackPostStub.getCalls()[0].args[1].tagOwnerMemberId).to.eql('test_member_id'); ; + expect(trackPostStub.getCalls()[0].args[1].dataSources.length).to.eql(1); + expect(trackPostStub.getCalls()[0].args[1].dataRecipients.length).to.eql(2); + expect(trackPostStub.getCalls()[0].args[1].publisherMemberId).to.eql('test_prebid_publisher'); done(); }, 100); }); From 6963bb33e01d6809fb6a641a9094fdf19bbe38d1 Mon Sep 17 00:00:00 2001 From: TheMediaGrid <44166371+TheMediaGrid@users.noreply.github.com> Date: Tue, 1 Dec 2020 18:22:59 +0300 Subject: [PATCH 060/103] grid Bid Adapter: Fix empty bidfloor (#6031) * Added TheMediaGridNM Bid Adapter * Updated required params for TheMediaGridNM Bid Adapter * Update TheMediGridNM Bid Adapter * Fix tests for TheMediaGridNM Bid Adapter * Fixes after review for TheMediaGridNM Bid Adapter * Add support of multi-format in TheMediaGrid Bid Adapter * Update sync url for grid and gridNM Bid Adapters * TheMediaGrid Bid Adapter: added keywords adUnit parameter * Update TheMediaGrid Bid Adapter to support keywords from config * Implement new request format for TheMediaGrid Bid Adapter * Fix jwpseg params for TheMediaGrid Bid Adapter * Update unit tests for The Media Grid Bid Adapter * Fix typo in TheMediaGrid Bid Adapter * Added test for jwTargeting in TheMediaGrid Bid Adapter * The new request format was made by default in TheMediaGrid Bid Adapter * Update userId format in ad request for TheMediaGrid Bid Adapter * Added bidFloor parameter for TheMediaGrid Bid Adapter * Fix for review TheMediaGrid Bid Adapter * Support floorModule in TheMediaGrid Bid Adapter * Fix empty bidfloor for TheMediaGrid Bid Adapter * Some change to restart autotests --- modules/gridBidAdapter.js | 15 +++++++++------ test/spec/modules/gridBidAdapter_spec.js | 8 +------- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/modules/gridBidAdapter.js b/modules/gridBidAdapter.js index db1402ea9ad..3f9d4fba31d 100644 --- a/modules/gridBidAdapter.js +++ b/modules/gridBidAdapter.js @@ -72,11 +72,12 @@ export const spec = { if (!userId) { userId = bid.userId; } - const {params: {uid, keywords, bidFloor}, mediaTypes, bidId, adUnitCode, rtd} = bid; + const {params: {uid, keywords}, mediaTypes, bidId, adUnitCode, rtd} = bid; bidsMap[bidId] = bid; if (!pageKeywords && !utils.isEmpty(keywords)) { pageKeywords = utils.transformBidderParamKeywords(keywords); } + const bidFloor = _getFloor(mediaTypes || {}, bid); const jwTargeting = rtd && rtd.jwplayer && rtd.jwplayer.targeting; if (jwTargeting) { if (!jwpseg && jwTargeting.segments) { @@ -91,10 +92,13 @@ export const spec = { tagid: uid.toString(), ext: { divid: adUnitCode - }, - bidfloor: _getFloor(mediaTypes || {}, bidFloor, bid) + } }; + if (bidFloor) { + impObj.bidfloor = bidFloor; + } + if (!mediaTypes || mediaTypes[BANNER]) { const banner = createBannerRequest(bid, mediaTypes ? mediaTypes[BANNER] : {}); if (banner) { @@ -323,13 +327,12 @@ export const spec = { /** * Gets bidfloor * @param {Object} mediaTypes - * @param {Number} bidfloor * @param {Object} bid * @returns {Number} floor */ -function _getFloor (mediaTypes, bidfloor, bid) { +function _getFloor (mediaTypes, bid) { const curMediaType = mediaTypes.video ? 'video' : 'banner'; - let floor = bidfloor || 0; + let floor = bid.params.bidFloor || 0; if (typeof bid.getFloor === 'function') { const floorInfo = bid.getFloor({ diff --git a/test/spec/modules/gridBidAdapter_spec.js b/test/spec/modules/gridBidAdapter_spec.js index 640bfda1fee..e884df40c5e 100644 --- a/test/spec/modules/gridBidAdapter_spec.js +++ b/test/spec/modules/gridBidAdapter_spec.js @@ -173,7 +173,6 @@ describe('TheMediaGrid Adapter', function () { 'id': bidRequests[1].bidId, 'tagid': bidRequests[1].params.uid, 'ext': {'divid': bidRequests[1].adUnitCode}, - 'bidfloor': 0, 'banner': { 'w': 300, 'h': 250, @@ -211,7 +210,6 @@ describe('TheMediaGrid Adapter', function () { 'id': bidRequests[1].bidId, 'tagid': bidRequests[1].params.uid, 'ext': {'divid': bidRequests[1].adUnitCode}, - 'bidfloor': 0, 'banner': { 'w': 300, 'h': 250, @@ -221,7 +219,6 @@ describe('TheMediaGrid Adapter', function () { 'id': bidRequests[2].bidId, 'tagid': bidRequests[2].params.uid, 'ext': {'divid': bidRequests[2].adUnitCode}, - 'bidfloor': 0, 'video': { 'w': 400, 'h': 600, @@ -259,7 +256,6 @@ describe('TheMediaGrid Adapter', function () { 'id': bidRequests[1].bidId, 'tagid': bidRequests[1].params.uid, 'ext': {'divid': bidRequests[1].adUnitCode}, - 'bidfloor': 0, 'banner': { 'w': 300, 'h': 250, @@ -269,7 +265,6 @@ describe('TheMediaGrid Adapter', function () { 'id': bidRequests[2].bidId, 'tagid': bidRequests[2].params.uid, 'ext': {'divid': bidRequests[2].adUnitCode}, - 'bidfloor': 0, 'video': { 'w': 400, 'h': 600, @@ -279,7 +274,6 @@ describe('TheMediaGrid Adapter', function () { 'id': bidRequests[3].bidId, 'tagid': bidRequests[3].params.uid, 'ext': {'divid': bidRequests[3].adUnitCode}, - 'bidfloor': 0, 'banner': { 'w': 728, 'h': 90, @@ -460,7 +454,7 @@ describe('TheMediaGrid Adapter', function () { 'floor': 1.50 }; const bidRequest = Object.assign({ - getFloor: _ => { + getFloor: (_) => { return floorTestData; } }, bidRequests[1]); From 804d76c03ff9b6d55acaeb85a0072e437b67ccfb Mon Sep 17 00:00:00 2001 From: Ben Anderson Date: Tue, 1 Dec 2020 11:38:49 -0500 Subject: [PATCH 061/103] Add fabrick to eids file (#6022) * Add entry of fabrickId in Eids #6021 * Add entry of fabrickId in Eids #6021 * Add entry of fabrickId in Eids #6021 * Add entry of fabrickId in Eids #6021 Co-authored-by: Anderson, Ben --- modules/userId/eids.js | 6 ++++++ modules/userId/eids.md | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/modules/userId/eids.js b/modules/userId/eids.js index 8118607fbde..2c627416341 100644 --- a/modules/userId/eids.js +++ b/modules/userId/eids.js @@ -162,6 +162,12 @@ const USER_IDS_CONFIG = { 'vmuid': { source: 'verizonmedia.com', atype: 1 + }, + + // Neustar Fabrick + 'fabrickId': { + source: 'neustar.biz', + atype: 1 } }; diff --git a/modules/userId/eids.md b/modules/userId/eids.md index 3e51eff3165..0cf9b6d2d22 100644 --- a/modules/userId/eids.md +++ b/modules/userId/eids.md @@ -21,6 +21,14 @@ userIdAsEids = [ }] }, + { + source: 'neustar.biz', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] + }, + { source: 'id5-sync.com', uids: [{ From 1739fafdb35be2717236bbdd028302e97c976ec2 Mon Sep 17 00:00:00 2001 From: Matt Kendall <1870166+mkendall07@users.noreply.github.com> Date: Tue, 1 Dec 2020 16:37:43 -0500 Subject: [PATCH 062/103] Added pubProvidedIdSystem to .submodules.json (#6056) Seemed to be missing, so added it. --- modules/.submodules.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/.submodules.json b/modules/.submodules.json index 36ead7df888..4e02391129a 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -18,7 +18,8 @@ "quantcastIdSystem", "idxIdSystem", "fabrickIdSystem", - "verizonMediaIdSystem" + "verizonMediaIdSystem", + "pubProvidedIdSystem" ], "adpod": [ "freeWheelAdserverVideo", From 30fa056a74652f2a6c0bf6a279b36e7c35a46f88 Mon Sep 17 00:00:00 2001 From: Krushmedia <71434282+Krushmedia@users.noreply.github.com> Date: Wed, 2 Dec 2020 01:22:01 +0200 Subject: [PATCH 063/103] Add getUserSync implementation into adapter (#6052) * Add getUserSync implementation into adapter --- modules/krushmediaBidAdapter.js | 19 ++++++++++++++ .../spec/modules/krushmediaBidAdapter_spec.js | 25 +++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/modules/krushmediaBidAdapter.js b/modules/krushmediaBidAdapter.js index f70500cc101..de1cce503e3 100644 --- a/modules/krushmediaBidAdapter.js +++ b/modules/krushmediaBidAdapter.js @@ -4,6 +4,7 @@ import * as utils from '../src/utils.js'; const BIDDER_CODE = 'krushmedia'; const AD_URL = 'https://ads4.krushmedia.com/?c=rtb&m=hb'; +const SYNC_URL = 'https://cs.krushmedia.com/html?src=pbjs' function isBidResponseValid(bid) { if (!bid.requestId || !bid.cpm || !bid.creativeId || @@ -99,6 +100,24 @@ export const spec = { } return response; }, + + getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { + let syncUrl = SYNC_URL + 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}`; + } + return [{ + type: 'iframe', + url: syncUrl + }]; + } }; registerBidder(spec); diff --git a/test/spec/modules/krushmediaBidAdapter_spec.js b/test/spec/modules/krushmediaBidAdapter_spec.js index 2673627bc6d..3af9ed64c43 100644 --- a/test/spec/modules/krushmediaBidAdapter_spec.js +++ b/test/spec/modules/krushmediaBidAdapter_spec.js @@ -301,4 +301,29 @@ describe('KrushmediabBidAdapter', function () { 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('iframe') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('https://cs.krushmedia.com/html?src=pbjs&gdpr=1&gdpr_consent=ALL') + }); + it('Should return array of objects with proper sync config , include CCPA', function() { + const syncData = spec.getUserSyncs({}, {}, {}, { + consentString: '1NNN' + }); + 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('iframe') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('https://cs.krushmedia.com/html?src=pbjs&ccpa_consent=1NNN') + }); + }); }); From a52e943192d039fd13184fbe2118db9d5a4fe06a Mon Sep 17 00:00:00 2001 From: Slind14 Date: Wed, 2 Dec 2020 13:38:09 +0100 Subject: [PATCH 064/103] .babelrc.js - changed IE target from 10 to 11 (#6035) IE 10 support was dropped a long time ago --- .babelrc.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.babelrc.js b/.babelrc.js index bece57ec4a5..2fa95d0716e 100644 --- a/.babelrc.js +++ b/.babelrc.js @@ -20,7 +20,7 @@ module.exports = { "safari >=8", "edge >= 14", "ff >= 57", - "ie >= 10", + "ie >= 11", "ios >= 8" ] } From ca452445ff8c43f05e1b2d686a99f5e558a9f07b Mon Sep 17 00:00:00 2001 From: Harshad Mane Date: Wed, 2 Dec 2020 06:22:09 -0800 Subject: [PATCH 065/103] 6060 Fix for: Changing globalVarName causes gulp serve tests to fail (#6069) * added support for pubcommon, digitrust, id5id * added support for IdentityLink * changed the source for id5 * added unit test cases * changed source param for identityLink * fixed the breaking test case when globalVarName is changed --- test/spec/modules/richaudienceBidAdapter_spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/spec/modules/richaudienceBidAdapter_spec.js b/test/spec/modules/richaudienceBidAdapter_spec.js index 1c710c46ea2..bdf50f2d7f5 100644 --- a/test/spec/modules/richaudienceBidAdapter_spec.js +++ b/test/spec/modules/richaudienceBidAdapter_spec.js @@ -348,7 +348,7 @@ describe('Richaudience adapter tests', function () { }); describe('UID test', function () { - pbjs.setConfig({ + config.setConfig({ consentManagement: { cmpApi: 'iab', timeout: 5000, @@ -739,7 +739,7 @@ describe('Richaudience adapter tests', function () { }, [], {consentString: '', gdprApplies: true}); expect(syncs).to.have.lengthOf(0); - pbjs.setConfig({ + config.setConfig({ consentManagement: { cmpApi: 'iab', timeout: 5000, From 03ed22133e4ebe8db68ff09bfe59e6af418e421c Mon Sep 17 00:00:00 2001 From: steve-a-districtm <56413795+steve-a-districtm@users.noreply.github.com> Date: Wed, 2 Dec 2020 09:57:13 -0500 Subject: [PATCH 066/103] update ttl value (#6041) * allow users to be sent to dmx even when gdpr is configured in prebid * Change default ttl value Change default ttl value for banner and video Co-authored-by: Menelik Tucker Co-authored-by: Tucker <72400387+MenelikTucker-districtm@users.noreply.github.com> --- modules/districtmDMXBidAdapter.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/districtmDMXBidAdapter.js b/modules/districtmDMXBidAdapter.js index 845cff460ee..60f6b9b64b1 100644 --- a/modules/districtmDMXBidAdapter.js +++ b/modules/districtmDMXBidAdapter.js @@ -39,9 +39,11 @@ export const spec = { nBid.requestId = nBid.impid; nBid.width = nBid.w || width; nBid.height = nBid.h || height; + nBid.ttl = 360; nBid.mediaType = bid.mediaTypes && bid.mediaTypes.video ? 'video' : 'banner'; if (nBid.mediaType) { nBid.vastXml = cleanVast(nBid.adm, nBid.nurl); + nBid.ttl = 3600; } if (nBid.dealid) { nBid.dealId = nBid.dealid; @@ -51,7 +53,6 @@ export const spec = { nBid.netRevenue = true; nBid.creativeId = nBid.crid; nBid.currency = 'USD'; - nBid.ttl = 60; nBid.meta = nBid.meta || {}; if (nBid.adomain && nBid.adomain.length > 0) { nBid.meta.advertiserDomains = nBid.adomain; From 6d29360058627a2d520aa33a9b1590f744d6815c Mon Sep 17 00:00:00 2001 From: Adam Browning <19834421+adam-browning@users.noreply.github.com> Date: Wed, 2 Dec 2020 17:53:34 +0200 Subject: [PATCH 067/103] oneVideo Adapter - Custom Key Value Pair targeting support (SAPR-15478) (#6053) * Custom key value pair support * validate values are string or number terary * validate values are string or number if statement * custom key value pair unit tests * restored LiveIntentIdSystem failing unit tests * keep version 3.0.4 * fix double quotes eslint * updated md file --- modules/oneVideoBidAdapter.js | 8 +++ modules/oneVideoBidAdapter.md | 4 ++ test/spec/modules/oneVideoBidAdapter_spec.js | 58 ++++++++++++++++++++ 3 files changed, 70 insertions(+) diff --git a/modules/oneVideoBidAdapter.js b/modules/oneVideoBidAdapter.js index c5bc054ac04..c287bc2f3b7 100644 --- a/modules/oneVideoBidAdapter.js +++ b/modules/oneVideoBidAdapter.js @@ -302,6 +302,14 @@ function getRequestData(bid, consentData, bidRequest) { bidData.site.ref = 'https://verizonmedia.com'; bidData.tmax = 1000; } + if (bid.params.video.custom && Object.prototype.toString.call(bid.params.video.custom) === '[object Object]') { + bidData.imp[0].ext.custom = {}; + for (const key in bid.params.video.custom) { + if (typeof bid.params.video.custom[key] === 'string' || typeof bid.params.video.custom[key] === 'number') { + bidData.imp[0].ext.custom[key] = bid.params.video.custom[key]; + } + } + } return bidData; } diff --git a/modules/oneVideoBidAdapter.md b/modules/oneVideoBidAdapter.md index 72f251aac04..92958af9e83 100644 --- a/modules/oneVideoBidAdapter.md +++ b/modules/oneVideoBidAdapter.md @@ -40,6 +40,10 @@ Connects to Verizon Media's Video SSP (AKA ONE Video / Adap.tv) demand source to inventoryid: 123, minduration: 10, maxduration: 30, + custom: { + key1: "value1", + key2: 123345 + } }, site: { id: 1, diff --git a/test/spec/modules/oneVideoBidAdapter_spec.js b/test/spec/modules/oneVideoBidAdapter_spec.js index ae29bcd48ec..331ac8976e6 100644 --- a/test/spec/modules/oneVideoBidAdapter_spec.js +++ b/test/spec/modules/oneVideoBidAdapter_spec.js @@ -315,6 +315,64 @@ describe('OneVideoBidAdapter', function () { const schain = data.source.ext.schain; expect(schain.nodes[0].hp).to.equal(bidRequest.params.video.hp); }) + it('should not accept key values pairs if custom is Undefined ', function () { + bidRequest.params.video.custom = null; + const requests = spec.buildRequests([ bidRequest ], bidderRequest); + const data = requests[0].data; + expect(data.imp[0].ext.custom).to.be.undefined; + }); + it('should not accept key values pairs if custom is Array ', function () { + bidRequest.params.video.custom = []; + const requests = spec.buildRequests([ bidRequest ], bidderRequest); + const data = requests[0].data; + expect(data.imp[0].ext.custom).to.be.undefined; + }); + it('should not accept key values pairs if custom is Number ', function () { + bidRequest.params.video.custom = 123456; + const requests = spec.buildRequests([ bidRequest ], bidderRequest); + const data = requests[0].data; + expect(data.imp[0].ext.custom).to.be.undefined; + }); + it('should not accept key values pairs if custom is String ', function () { + bidRequest.params.video.custom = 'keyValuePairs'; + const requests = spec.buildRequests([ bidRequest ], bidderRequest); + const data = requests[0].data; + expect(data.imp[0].ext.custom).to.be.undefined; + }); + it('should not accept key values pairs if custom is Boolean ', function () { + bidRequest.params.video.custom = true; + const requests = spec.buildRequests([ bidRequest ], bidderRequest); + const data = requests[0].data; + expect(data.imp[0].ext.custom).to.be.undefined; + }); + it('should accept key values pairs if custom is Object ', function () { + bidRequest.params.video.custom = {}; + const requests = spec.buildRequests([ bidRequest ], bidderRequest); + const data = requests[0].data; + expect(data.imp[0].ext.custom).to.be.a('object'); + }); + it('should accept key values pairs if custom is Object ', function () { + bidRequest.params.video.custom = { + key1: 'value1', + key2: 'value2', + key3: 4444444, + key4: false, + key5: {nested: 'object'}, + key6: ['string', 2, true, null], + key7: null, + key8: undefined + }; + const requests = spec.buildRequests([ bidRequest ], bidderRequest); + const custom = requests[0].data.imp[0].ext.custom; + expect(custom['key1']).to.be.a('string'); + expect(custom['key2']).to.be.a('string'); + expect(custom['key3']).to.be.a('number'); + expect(custom['key4']).to.not.exist; + expect(custom['key5']).to.not.exist; + expect(custom['key6']).to.not.exist; + expect(custom['key7']).to.not.exist; + expect(custom['key8']).to.not.exist; + }); }); describe('spec.interpretResponse', function () { From 53b0ed0fbe9fca4cb8bd45d583db6a0de767ae6f Mon Sep 17 00:00:00 2001 From: Neelanjan Sen <14229985+Fawke@users.noreply.github.com> Date: Wed, 2 Dec 2020 23:27:04 +0530 Subject: [PATCH 068/103] appnexusBidAdapter - remove tpuids (#6074) --- modules/appnexusBidAdapter.js | 6 ------ test/spec/modules/appnexusBidAdapter_spec.js | 7 +------ 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index f714472bbb1..203835db611 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -231,16 +231,10 @@ export const spec = { const criteoId = utils.deepAccess(bidRequests[0], `userId.criteoId`); let eids = []; if (criteoId) { - let tpuids = []; - tpuids.push({ - 'provider': 'criteo', - 'user_id': criteoId - }); eids.push({ source: 'criteo.com', id: criteoId }); - payload.tpuids = tpuids; } const tdid = utils.deepAccess(bidRequests[0], `userId.tdid`); diff --git a/test/spec/modules/appnexusBidAdapter_spec.js b/test/spec/modules/appnexusBidAdapter_spec.js index 21d3da358be..426259639e8 100644 --- a/test/spec/modules/appnexusBidAdapter_spec.js +++ b/test/spec/modules/appnexusBidAdapter_spec.js @@ -808,7 +808,7 @@ describe('AppNexusAdapter', function () { expect(request.options).to.deep.equal({withCredentials: false}); }); - it('should populate eids and tpuids when ttd id and criteo is available', function () { + it('should populate eids when ttd id and criteo is available', function () { const bidRequest = Object.assign({}, bidRequests[0], { userId: { tdid: 'sample-userid', @@ -828,11 +828,6 @@ describe('AppNexusAdapter', function () { source: 'criteo.com', id: 'sample-criteo-userid', }); - - expect(payload.tpuids).to.deep.include({ - provider: 'criteo', - user_id: 'sample-criteo-userid', - }); }); it('should populate iab_support object at the root level if omid support is detected', function () { From bb501f062dbcadfa1e6554b9b33d5471e9b79e71 Mon Sep 17 00:00:00 2001 From: mmoschovas <63253416+mmoschovas@users.noreply.github.com> Date: Wed, 2 Dec 2020 13:36:36 -0500 Subject: [PATCH 069/103] Rubicon Bid Adapter remove rp_floor param if floor not set (#6062) * RP bid adapter update to not set rp_floor when floor param does not exist. Left logic to set rp_floor to value if above 0.01. If floor param exists and equals 0.01 or below, 0.01 will be passed * Updated floor logic to be if a value is set greater than or equal to 0.01 then pass it otherwise dont set rp_floor --- modules/rubiconBidAdapter.js | 2 +- test/spec/modules/rubiconBidAdapter_spec.js | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index b44ae108b38..e439f7fd2a4 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -460,7 +460,7 @@ export const spec = { 'zone_id': params.zoneId, 'size_id': parsedSizes[0], 'alt_size_ids': parsedSizes.slice(1).join(',') || undefined, - 'rp_floor': (params.floor = parseFloat(params.floor)) > 0.01 ? params.floor : 0.01, + 'rp_floor': (params.floor = parseFloat(params.floor)) >= 0.01 ? params.floor : undefined, 'rp_secure': '1', 'tk_flint': `${rubiConf.int_type || DEFAULT_INTEGRATION}_v$prebid.version$`, 'x_source.tid': bidRequest.transactionId, diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index b1ef02a6369..6944034a787 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -396,7 +396,10 @@ describe('the rubicon adapter', function () { describe('to fastlane', function () { it('should make a well-formed request object', function () { sandbox.stub(Math, 'random').callsFake(() => 0.1); - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let duplicate = Object.assign(bidderRequest); + duplicate.bids[0].params.floor = 0.01; + + let [request] = spec.buildRequests(duplicate.bids, duplicate); let data = parseQuery(request.data); expect(request.url).to.equal('https://fastlane.rubiconproject.com/a/api/fastlane.json'); @@ -551,7 +554,7 @@ describe('the rubicon adapter', function () { sandbox.stub(Math, 'random').callsFake(() => 0.1); let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - const referenceOrdering = ['account_id', 'site_id', 'zone_id', 'size_id', 'alt_size_ids', 'p_pos', 'rf', 'p_geo.latitude', 'p_geo.longitude', 'kw', 'tg_v.ucat', 'tg_v.lastsearch', 'tg_v.likes', 'tg_i.rating', 'tg_i.prodtype', 'tk_flint', 'x_source.tid', 'x_source.pchain', 'p_screen_res', 'rp_floor', 'rp_secure', 'tk_user_key', 'tg_fl.eid', 'slots', 'rand']; + const referenceOrdering = ['account_id', 'site_id', 'zone_id', 'size_id', 'alt_size_ids', 'p_pos', 'rf', 'p_geo.latitude', 'p_geo.longitude', 'kw', 'tg_v.ucat', 'tg_v.lastsearch', 'tg_v.likes', 'tg_i.rating', 'tg_i.prodtype', 'tk_flint', 'x_source.tid', 'x_source.pchain', 'p_screen_res', 'rp_secure', 'tk_user_key', 'tg_fl.eid', 'slots', 'rand']; request.data.split('&').forEach((item, i) => { expect(item.split('=')[0]).to.equal(referenceOrdering[i]); @@ -566,7 +569,6 @@ describe('the rubicon adapter', function () { 'size_id': '15', 'alt_size_ids': '43', 'p_pos': 'atf', - 'rp_floor': '0.01', 'rp_secure': /[01]/, 'rand': '0.1', 'tk_flint': INTEGRATION, @@ -883,7 +885,6 @@ describe('the rubicon adapter', function () { 'size_id': '15', 'alt_size_ids': '43', 'p_pos': 'atf', - 'rp_floor': '0.01', 'rp_secure': /[01]/, 'rand': '0.1', 'tk_flint': INTEGRATION, @@ -2047,7 +2048,6 @@ describe('the rubicon adapter', function () { 'size_id': 15, 'alt_size_ids': '43', 'p_pos': 'atf', - 'rp_floor': 0.01, 'rp_secure': /[01]/, 'tk_flint': INTEGRATION, 'x_source.tid': 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', From 571c6a1b0357ec234ba8548534319e9a3f9b1eca Mon Sep 17 00:00:00 2001 From: Jason Snellbaker Date: Wed, 2 Dec 2020 15:40:01 -0500 Subject: [PATCH 070/103] Prebid 4.18.0 Release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 48963f36c76..799c730e3cd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.18.0-pre", + "version": "4.18.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From b8eb346f72980ce126240efaee87488bb53ffa8f Mon Sep 17 00:00:00 2001 From: Jason Snellbaker Date: Wed, 2 Dec 2020 15:53:46 -0500 Subject: [PATCH 071/103] increment pre version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 799c730e3cd..ed4514cee42 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.18.0", + "version": "4.19.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From cfe9786a282b3bb6acc9495ca59524b5ba3909ed Mon Sep 17 00:00:00 2001 From: David Carver <54326287+ndxcarver@users.noreply.github.com> Date: Wed, 2 Dec 2020 15:45:16 -0600 Subject: [PATCH 072/103] LKQD: update adapter to include new parameters (#6033) * LKQD: update adapter to include new parameters * LKQD: update to use standardized coppa * LKQD: convert true to 1 for api --- modules/lkqdBidAdapter.js | 10 ++++++++ test/spec/modules/lkqdBidAdapter_spec.js | 29 ++++++++++++++++++++++-- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/modules/lkqdBidAdapter.js b/modules/lkqdBidAdapter.js index 51d5c48e1fc..0f5782649ad 100644 --- a/modules/lkqdBidAdapter.js +++ b/modules/lkqdBidAdapter.js @@ -1,6 +1,7 @@ import * as utils from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { VIDEO } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; const BIDDER_CODE = 'lkqd'; const BID_TTL_DEFAULT = 300; @@ -148,6 +149,9 @@ function buildRequests(validBidRequests, bidderRequest) { if (bidRequest.params.hasOwnProperty('dnt') && bidRequest.params.dnt != null) { sspData.dnt = bidRequest.params.dnt; } + if (config.getConfig('coppa') === true) { + sspData.coppa = 1; + } if (bidRequest.params.hasOwnProperty('pageurl') && bidRequest.params.pageurl != null) { sspData.pageurl = bidRequest.params.pageurl; } else if (bidderRequest && bidderRequest.refererInfo) { @@ -177,6 +181,12 @@ function buildRequests(validBidRequests, bidderRequest) { sspData.bidWidth = playerWidth; sspData.bidHeight = playerHeight; + for (let k = 1; k <= 40; k++) { + if (bidRequest.params.hasOwnProperty(`c${k}`) && bidRequest.params[`c${k}`]) { + sspData[`c${k}`] = bidRequest.params[`c${k}`]; + } + } + bidRequests.push({ method: 'GET', url: sspUrl, diff --git a/test/spec/modules/lkqdBidAdapter_spec.js b/test/spec/modules/lkqdBidAdapter_spec.js index f10d936a28b..1cd33d9ec59 100644 --- a/test/spec/modules/lkqdBidAdapter_spec.js +++ b/test/spec/modules/lkqdBidAdapter_spec.js @@ -1,6 +1,7 @@ import { spec } from 'modules/lkqdBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; -const { expect } = require('chai'); +import { config } from 'src/config.js'; +import { expect } from 'chai'; describe('LKQD Bid Adapter Test', () => { const adapter = newBidder(spec); @@ -47,7 +48,9 @@ describe('LKQD Bid Adapter Test', () => { 'bidder': 'lkqd', 'params': { 'siteId': '662921', - 'placementId': '263' + 'placementId': '263', + 'c1': 'newWindow', + 'c20': 'lkqdCustom' }, 'adUnitCode': 'lkqd', 'sizes': [[300, 250], [640, 480]], @@ -75,6 +78,10 @@ describe('LKQD Bid Adapter Test', () => { ]; it('should populate available parameters', () => { + sinon.stub(config, 'getConfig') + .withArgs('coppa') + .returns(true); + const requests = spec.buildRequests(bidRequests); expect(requests.length).to.equal(2); const r1 = requests[0].data; @@ -82,14 +89,26 @@ describe('LKQD Bid Adapter Test', () => { expect(r1).to.have.string('&sid=662921&'); expect(r1).to.have.string('&width=300&'); expect(r1).to.have.string('&height=250&'); + expect(r1).to.have.string('&coppa=1&'); + expect(r1).to.have.string('&c1=newWindow&'); + expect(r1).to.have.string('&c20=lkqdCustom'); const r2 = requests[1].data; expect(r2).to.have.string('pid=263&'); expect(r2).to.have.string('&sid=662921&'); expect(r2).to.have.string('&width=640&'); expect(r2).to.have.string('&height=480&'); + expect(r2).to.have.string('&coppa=1&'); + expect(r2).to.have.string('&c1=newWindow&'); + expect(r2).to.have.string('&c20=lkqdCustom'); + + config.getConfig.restore(); }); it('should not populate unspecified parameters', () => { + sinon.stub(config, 'getConfig') + .withArgs('coppa') + .returns(false); + const requests = spec.buildRequests(bidRequests); expect(requests.length).to.equal(2); const r1 = requests[0].data; @@ -99,6 +118,8 @@ describe('LKQD Bid Adapter Test', () => { expect(r1).to.not.have.string('&contentlength='); expect(r1).to.not.have.string('&contenturl='); expect(r1).to.not.have.string('&schain='); + expect(r1).to.not.have.string('&c10='); + expect(r1).to.not.have.string('coppa'); const r2 = requests[1].data; expect(r2).to.not.have.string('&dnt='); expect(r2).to.not.have.string('&contentid='); @@ -106,6 +127,10 @@ describe('LKQD Bid Adapter Test', () => { expect(r2).to.not.have.string('&contentlength='); expect(r2).to.not.have.string('&contenturl='); expect(r2).to.not.have.string('&schain='); + expect(r2).to.not.have.string('&c39='); + expect(r2).to.not.have.string('coppa'); + + config.getConfig.restore(); }); it('should handle single size request', () => { From 19da021b34207fabc36686ee0ea6a2b2fc7d724e Mon Sep 17 00:00:00 2001 From: Meng <5110935+edmonl@users.noreply.github.com> Date: Thu, 3 Dec 2020 10:07:06 -0500 Subject: [PATCH 073/103] pubGENIUS bid adapter: support video (#6040) * pubGENIUS bid adapter: support video * update md doc to show more video params --- modules/pubgeniusBidAdapter.js | 93 ++++++++++++-- modules/pubgeniusBidAdapter.md | 35 +++++- test/spec/modules/pubgeniusBidAdapter_spec.js | 116 +++++++++++++++++- 3 files changed, 229 insertions(+), 15 deletions(-) diff --git a/modules/pubgeniusBidAdapter.js b/modules/pubgeniusBidAdapter.js index 55f50e4b6a9..88e85a4fd7a 100644 --- a/modules/pubgeniusBidAdapter.js +++ b/modules/pubgeniusBidAdapter.js @@ -1,7 +1,7 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { ajax } from '../src/ajax.js'; import { config } from '../src/config.js'; -import { BANNER } from '../src/mediaTypes.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { deepAccess, deepSetValue, @@ -12,15 +12,16 @@ import { isStr, logError, parseQueryStringParameters, + pick, } from '../src/utils.js'; -const BIDDER_VERSION = '1.0.0'; +const BIDDER_VERSION = '1.1.0'; const BASE_URL = 'https://ortb.adpearl.io'; export const spec = { code: 'pubgenius', - supportedMediaTypes: [ BANNER ], + supportedMediaTypes: [ BANNER, VIDEO ], isBidRequestValid(bid) { const adUnitId = bid.params.adUnitId; @@ -29,8 +30,13 @@ export const spec = { return false; } - const sizes = deepAccess(bid, 'mediaTypes.banner.sizes'); - return !!(sizes && sizes.length) && sizes.every(size => isArrayOfNums(size, 2)); + const { mediaTypes } = bid; + + if (mediaTypes.banner) { + return isValidBanner(mediaTypes.banner); + } + + return isValidVideo(mediaTypes.video, bid.params.video); }, buildRequests: function (bidRequests, bidderRequest) { @@ -141,16 +147,44 @@ export const spec = { }, }; +function buildVideoParams(videoMediaType, videoParams) { + videoMediaType = videoMediaType || {}; + const params = pick(videoMediaType, ['api', 'mimes', 'protocols', 'playbackmethod']); + + switch (videoMediaType.context) { + case 'instream': + params.placement = 1; + break; + case 'outstream': + params.placement = 2; + break; + default: + break; + } + + if (videoMediaType.playerSize) { + params.w = videoMediaType.playerSize[0][0]; + params.h = videoMediaType.playerSize[0][1]; + } + + return Object.assign(params, videoParams); +} + function buildImp(bid) { const imp = { id: bid.bidId, - banner: { - format: deepAccess(bid, 'mediaTypes.banner.sizes').map(size => ({ w: size[0], h: size[1] })), - topframe: numericBoolean(!inIframe()), - }, tagid: String(bid.params.adUnitId), }; + if (bid.mediaTypes.banner) { + imp.banner = { + format: bid.mediaTypes.banner.sizes.map(size => ({ w: size[0], h: size[1] })), + topframe: numericBoolean(!inIframe()), + }; + } else { + imp.video = buildVideoParams(bid.mediaTypes.video, bid.params.video); + } + const bidFloor = bid.params.bidFloor; if (isNumber(bidFloor)) { imp.bidfloor = bidFloor; @@ -197,7 +231,6 @@ function interpretBid(bid) { cpm: bid.price, width: bid.w, height: bid.h, - ad: bid.adm, ttl: bid.exp, creativeId: bid.crid, netRevenue: true, @@ -209,6 +242,28 @@ function interpretBid(bid) { }; } + const pbadapter = deepAccess(bid, 'ext.pbadapter') || {}; + switch (pbadapter.mediaType) { + case 'video': + if (bid.nurl) { + bidResponse.vastUrl = bid.nurl; + } + + if (bid.adm) { + bidResponse.vastXml = bid.adm; + } + + if (pbadapter.cacheKey) { + bidResponse.videoCacheKey = pbadapter.cacheKey; + } + + bidResponse.mediaType = VIDEO; + break; + default: // banner by default + bidResponse.ad = bid.adm; + break; + } + return bidResponse; } @@ -221,4 +276,22 @@ function getBaseUrl() { return (pubg && pubg.endpoint) || BASE_URL; } +function isValidSize(size) { + return isArrayOfNums(size, 2) && size[0] > 0 && size[1] > 0; +} + +function isValidBanner(banner) { + const sizes = banner.sizes; + return !!(sizes && sizes.length) && sizes.every(isValidSize); +} + +function isValidVideo(videoMediaType, videoParams) { + const params = buildVideoParams(videoMediaType, videoParams); + + return !!(params.placement && + isValidSize([params.w, params.h]) && + params.mimes && params.mimes.length && + isArrayOfNums(params.protocols) && params.protocols.length); +} + registerBidder(spec); diff --git a/modules/pubgeniusBidAdapter.md b/modules/pubgeniusBidAdapter.md index 66851af9c3f..ff23a433331 100644 --- a/modules/pubgeniusBidAdapter.md +++ b/modules/pubgeniusBidAdapter.md @@ -50,7 +50,40 @@ var adUnits = [ } } ] - } + }, + { + code: 'test-video', + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 360], + mimes: ['video/mp4'], + protocols: [3], + } + }, + bids: [ + { + bidder: 'pubgenius', + params: { + adUnitId: '1001', + bidFloor: 1, + test: true, + + // other video parameters as in OpenRTB v2.5 spec + video: { + skip: 1 + + // the following overrides mediaTypes.video of the ad unit + placement: 1, + w: 640, + h: 360, + mimes: ['video/mp4'], + protocols: [3], + } + } + } + ] + }, ]; ``` diff --git a/test/spec/modules/pubgeniusBidAdapter_spec.js b/test/spec/modules/pubgeniusBidAdapter_spec.js index 4b5bf7efac0..c0b05510707 100644 --- a/test/spec/modules/pubgeniusBidAdapter_spec.js +++ b/test/spec/modules/pubgeniusBidAdapter_spec.js @@ -1,8 +1,9 @@ import { expect } from 'chai'; import { spec } from 'modules/pubgeniusBidAdapter.js'; -import { deepClone, parseQueryStringParameters } from 'src/utils.js'; import { config } from 'src/config.js'; +import { VIDEO } from 'src/mediaTypes.js'; +import { deepClone, parseQueryStringParameters } from 'src/utils.js'; import { server } from 'test/mocks/xhr.js'; const { @@ -23,8 +24,8 @@ describe('pubGENIUS adapter', () => { }); describe('supportedMediaTypes', () => { - it('should contain only banner', () => { - expect(supportedMediaTypes).to.deep.equal(['banner']); + it('should contain banner and video', () => { + expect(supportedMediaTypes).to.deep.equal(['banner', 'video']); }); }); @@ -77,6 +78,51 @@ describe('pubGENIUS adapter', () => { expect(isBidRequestValid(bid)).to.be.false; }); + + it('should return false without banner or video', () => { + bid.mediaTypes = {}; + + expect(isBidRequestValid(bid)).to.be.false; + }); + + it('should return true with valid video media type', () => { + bid.mediaTypes = { + video: { + context: 'instream', + playerSize: [[100, 100]], + mimes: ['video/mp4'], + protocols: [1], + }, + }; + + expect(isBidRequestValid(bid)).to.be.true; + }); + + it('should return true with valid video params', () => { + bid.params.video = { + placement: 1, + w: 200, + h: 200, + mimes: ['video/mp4'], + protocols: [1], + }; + + expect(isBidRequestValid(bid)).to.be.true; + }); + + it('should return false without video protocols', () => { + bid.mediaTypes = { + video: { + context: 'instream', + playerSize: [[100, 100]], + }, + }; + bid.params.video = { + mimes: ['video/mp4'], + }; + + expect(isBidRequestValid(bid)).to.be.false; + }); }); describe('buildRequests', () => { @@ -143,7 +189,7 @@ describe('pubGENIUS adapter', () => { tmax: 1200, ext: { pbadapter: { - version: '1.0.0', + version: '1.1.0', }, }, }, @@ -314,6 +360,44 @@ describe('pubGENIUS adapter', () => { expect(buildRequests([bidRequest], bidderRequest)).to.deep.equal(expectedRequest); }); + + it('should build video imp', () => { + bidRequest.mediaTypes = { + video: { + context: 'instream', + playerSize: [[200, 100]], + mimes: ['video/mp4'], + protocols: [2, 3], + api: [1, 2], + playbackmethod: [3, 4], + }, + }; + bidRequest.params.video = { + minduration: 5, + maxduration: 100, + skip: 1, + skipafter: 1, + startdelay: -1, + }; + + delete expectedRequest.data.imp[0].banner; + expectedRequest.data.imp[0].video = { + mimes: ['video/mp4'], + minduration: 5, + maxduration: 100, + protocols: [2, 3], + w: 200, + h: 100, + startdelay: -1, + placement: 1, + skip: 1, + skipafter: 1, + playbackmethod: [3, 4], + api: [1, 2], + }; + + expect(buildRequests([bidRequest], bidderRequest)).to.deep.equal(expectedRequest); + }); }); describe('interpretResponse', () => { @@ -370,6 +454,30 @@ describe('pubGENIUS adapter', () => { it('should interpret no bids', () => { expect(interpretResponse({ body: {} })).to.deep.equal([]); }); + + it('should interpret video response', () => { + serverResponse.body.seatbid[0].bid[0] = { + ...serverResponse.body.seatbid[0].bid[0], + nurl: 'http://vasturl/cache?id=x', + ext: { + pbadapter: { + mediaType: 'video', + cacheKey: 'x', + }, + }, + }; + + delete expectedBidResponse.ad; + expectedBidResponse = { + ...expectedBidResponse, + vastUrl: 'http://vasturl/cache?id=x', + vastXml: 'fake_creative', + videoCacheKey: 'x', + mediaType: VIDEO, + }; + + expect(interpretResponse(serverResponse)).to.deep.equal([expectedBidResponse]); + }); }); describe('getUserSyncs', () => { From ddebc4a2f8efb0aaa9c2c26c260bc25c380599b0 Mon Sep 17 00:00:00 2001 From: Jenine Drew <2839303+jeninedrew@users.noreply.github.com> Date: Thu, 3 Dec 2020 21:12:29 -0500 Subject: [PATCH 074/103] Add Optional Params to Concert Adapter (#6064) --- modules/concertBidAdapter.js | 9 ++++++--- modules/concertBidAdapter.md | 8 ++++++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/modules/concertBidAdapter.js b/modules/concertBidAdapter.js index d153ddf9ee2..3eb75799705 100644 --- a/modules/concertBidAdapter.js +++ b/modules/concertBidAdapter.js @@ -42,7 +42,7 @@ export const spec = { debug: utils.debugTurnedOn(), uid: getUid(bidderRequest), optedOut: hasOptedOutOfPersonalization(), - adapterVersion: '1.1.0', + adapterVersion: '1.1.1', uspConsent: bidderRequest.uspConsent, gdprConsent: bidderRequest.gdprConsent } @@ -53,9 +53,12 @@ export const spec = { name: bidRequest.adUnitCode, bidId: bidRequest.bidId, transactionId: bidRequest.transactionId, - sizes: bidRequest.sizes, + sizes: bidRequest.params.sizes || bidRequest.sizes, partnerId: bidRequest.params.partnerId, - slotType: bidRequest.params.slotType + slotType: bidRequest.params.slotType, + adSlot: bidRequest.params.slot || bidRequest.adUnitCode, + placementId: bidRequest.params.placementId || '', + site: bidRequest.params.site || bidderRequest.refererInfo.referer } return slot; diff --git a/modules/concertBidAdapter.md b/modules/concertBidAdapter.md index faf774946d1..d8736082e5c 100644 --- a/modules/concertBidAdapter.md +++ b/modules/concertBidAdapter.md @@ -24,10 +24,14 @@ Module that connects to Concert demand sources { bidder: "concert", params: { - partnerId: 'test_partner' + partnerId: 'test_partner', + site: 'site_name', + placementId: 1234567, + slot: 'slot_name', + sizes: [[1030, 590]] } } ] } ]; -``` \ No newline at end of file +``` From 2b730a841b59335ed089269913f4a4d44145db63 Mon Sep 17 00:00:00 2001 From: Olivier Date: Fri, 4 Dec 2020 10:34:25 +0100 Subject: [PATCH 075/103] adagioBidAdapter: add Video support (#6038) * adagioBidAdapter: add outstream video support * Lint: semi rule consistency * IE11 support: remove Array.includes() * Generate bidResponse.vastUrl based on vastXml dataUri encoding * Update .md file --- modules/adagioBidAdapter.js | 163 ++++++++++++++++++--- modules/adagioBidAdapter.md | 58 +++++++- test/spec/modules/adagioBidAdapter_spec.js | 142 ++++++++++++++++-- 3 files changed, 327 insertions(+), 36 deletions(-) diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index b20f832fd42..cab233d5387 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -2,23 +2,27 @@ import find from 'core-js-pure/features/array/find.js'; import * as utils from '../src/utils.js'; import { config } from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import { loadExternalScript } from '../src/adloader.js' +import { loadExternalScript } from '../src/adloader.js'; import JSEncrypt from 'jsencrypt/bin/jsencrypt.js'; import sha256 from 'crypto-js/sha256.js'; import { getStorageManager } from '../src/storageManager.js'; import { getRefererInfo } from '../src/refererDetection.js'; import { createEidsArray } from './userId/eids.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { Renderer } from '../src/Renderer.js'; +import { OUTSTREAM } from '../src/video.js'; export const BIDDER_CODE = 'adagio'; export const LOG_PREFIX = 'Adagio:'; -export const VERSION = '2.5.0'; +export const VERSION = '2.6.0'; export const FEATURES_VERSION = '1'; export const ENDPOINT = 'https://mp.4dex.io/prebid'; -export const SUPPORTED_MEDIA_TYPES = ['banner']; +export const SUPPORTED_MEDIA_TYPES = [BANNER, VIDEO]; export const ADAGIO_TAG_URL = 'https://script.4dex.io/localstore.js'; export const ADAGIO_LOCALSTORAGE_KEY = 'adagioScript'; export const GVLID = 617; export const storage = getStorageManager(GVLID, 'adagio'); +export const RENDERER_URL = 'https://script.4dex.io/outstream-player.js'; export const ADAGIO_PUBKEY = `-----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC9el0+OEn6fvEh1RdVHQu4cnT0 @@ -27,6 +31,35 @@ t0b0lsHN+W4n9kitS/DZ/xnxWK/9vxhv0ZtL1LL/rwR5Mup7rmJbNtDoNBw4TIGj pV6EP3MTLosuUEpLaQIDAQAB -----END PUBLIC KEY-----`; +// This provide a whitelist and a basic validation +// of OpenRTB 2.5 options used by the Adagio SSP. +// https://www.iab.com/wp-content/uploads/2016/03/OpenRTB-API-Specification-Version-2-5-FINAL.pdf +export const ORTB_VIDEO_PARAMS = { + 'mimes': (value) => Array.isArray(value) && value.length > 0 && value.every(v => typeof v === 'string'), + 'minduration': (value) => utils.isInteger(value), + 'maxduration': (value) => utils.isInteger(value), + 'protocols': (value) => Array.isArray(value) && value.every(v => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].indexOf(v) !== -1), + 'w': (value) => utils.isInteger(value), + 'h': (value) => utils.isInteger(value), + 'startdelay': (value) => utils.isInteger(value), + 'placement': (value) => Array.isArray(value) && value.every(v => [1, 2, 3, 4, 5].indexOf(v) !== -1), + 'linearity': (value) => [1, 2].indexOf(value) !== -1, + 'skip': (value) => [0, 1].indexOf(value) !== -1, + 'skipmin': (value) => utils.isInteger(value), + 'skipafter': (value) => utils.isInteger(value), + 'sequence': (value) => utils.isInteger(value), + 'battr': (value) => Array.isArray(value) && value.every(v => Array.from({length: 17}, (_, i) => i + 1).indexOf(v) !== -1), + 'maxextended': (value) => utils.isInteger(value), + 'minbitrate': (value) => utils.isInteger(value), + 'maxbitrate': (value) => utils.isInteger(value), + 'boxingallowed': (value) => [0, 1].indexOf(value) !== -1, + 'playbackmethod': (value) => Array.isArray(value) && value.every(v => [1, 2, 3, 4, 5, 6].indexOf(v) !== -1), + 'playbackend': (value) => [1, 2, 3].indexOf(value) !== -1, + 'delivery': (value) => [1, 2, 3].indexOf(value) !== -1, + 'pos': (value) => [0, 1, 2, 3, 4, 5, 6, 7].indexOf(value) !== -1, + 'api': (value) => Array.isArray(value) && value.every(v => [1, 2, 3, 4, 5, 6].indexOf(v) !== -1) +}; + let currentWindow; export function adagioScriptFromLocalStorageCb(ls) { @@ -64,7 +97,7 @@ export function adagioScriptFromLocalStorageCb(ls) { export function getAdagioScript() { storage.getDataFromLocalStorage(ADAGIO_LOCALSTORAGE_KEY, (ls) => { - internal.adagioScriptFromLocalStorageCb(ls) + internal.adagioScriptFromLocalStorageCb(ls); }); storage.localStorageIsEnabled(isValid => { @@ -335,7 +368,7 @@ function getOrAddAdagioAdUnit(adUnitCode) { w.ADAGIO = w.ADAGIO || {}; if (w.ADAGIO.adUnits[adUnitCode]) { - return w.ADAGIO.adUnits[adUnitCode] + return w.ADAGIO.adUnits[adUnitCode]; } return w.ADAGIO.adUnits[adUnitCode] = {}; @@ -435,7 +468,7 @@ function getElementFromTopWindow(element, currentWindow) { }; function autoDetectAdUnitElementId(adUnitCode) { - const autoDetectedAdUnit = utils.getGptSlotInfoForAdUnitCode(adUnitCode) + const autoDetectedAdUnit = utils.getGptSlotInfoForAdUnitCode(adUnitCode); let adUnitElementId = null; if (autoDetectedAdUnit && autoDetectedAdUnit.divId) { @@ -450,16 +483,16 @@ function autoDetectEnvironment() { let environment; switch (device) { case 2: - environment = 'desktop' + environment = 'desktop'; break; case 4: - environment = 'mobile' + environment = 'mobile'; break; case 5: - environment = 'tablet' + environment = 'tablet'; break; }; - return environment + return environment; }; function getFeatures(bidRequest, bidderRequest) { @@ -507,6 +540,21 @@ function getFeatures(bidRequest, bidderRequest) { return features; }; +function isRendererPreferredFromPublisher(bidRequest) { + // renderer defined at adUnit level + const adUnitRenderer = utils.deepAccess(bidRequest, 'renderer'); + const hasValidAdUnitRenderer = !!(adUnitRenderer && adUnitRenderer.url && adUnitRenderer.render); + + // renderer defined at adUnit.mediaTypes level + const mediaTypeRenderer = utils.deepAccess(bidRequest, 'mediaTypes.video.renderer'); + const hasValidMediaTypeRenderer = !!(mediaTypeRenderer && mediaTypeRenderer.url && mediaTypeRenderer.render); + + return !!( + (hasValidAdUnitRenderer && !(adUnitRenderer.backupOnly === true)) || + (hasValidMediaTypeRenderer && !(mediaTypeRenderer.backupOnly === true)) + ); +} + export const internal = { enqueue, getOrAddAdagioAdUnit, @@ -521,7 +569,8 @@ export const internal = { getRefererInfo, adagioScriptFromLocalStorageCb, getCurrentWindow, - canAccessTopWindow + canAccessTopWindow, + isRendererPreferredFromPublisher }; function _getGdprConsent(bidderRequest) { @@ -539,7 +588,7 @@ function _getGdprConsent(bidderRequest) { const consent = {}; if (apiVersion !== undefined) { - consent.apiVersion = apiVersion + consent.apiVersion = apiVersion; } if (consentString !== undefined) { @@ -575,10 +624,62 @@ function _getSchain(bidRequest) { function _getEids(bidRequest) { if (utils.deepAccess(bidRequest, 'userId')) { - return createEidsArray(bidRequest.userId) + return createEidsArray(bidRequest.userId); } } +function _buildVideoBidRequest(bidRequest) { + const videoAdUnitParams = utils.deepAccess(bidRequest, 'mediaTypes.video', {}); + const videoBidderParams = utils.deepAccess(bidRequest, 'params.video', {}); + const computedParams = {}; + + // Special case for playerSize. + // Eeach props will be overrided if they are defined in config. + if (Array.isArray(videoAdUnitParams.playerSize)) { + const tempSize = (Array.isArray(videoAdUnitParams.playerSize[0])) ? videoAdUnitParams.playerSize[0] : videoAdUnitParams.playerSize; + computedParams.w = tempSize[0]; + computedParams.h = tempSize[1]; + } + + const videoParams = { + ...computedParams, + ...videoAdUnitParams, + ...videoBidderParams + }; + + if (videoParams.context && videoParams.context === OUTSTREAM) { + bidRequest.mediaTypes.video.playerName = (internal.isRendererPreferredFromPublisher(bidRequest)) ? 'other' : 'adagio'; + + if (bidRequest.mediaTypes.video.playerName === 'other') { + utils.logWarn(`${LOG_PREFIX} renderer.backupOnly has not been set. Adagio recommends to use its own player to get expected behavior.`); + } + } + + // Only whitelisted OpenRTB options need to be validated. + // Other options will still remain in the `mediaTypes.video` object + // sent in the ad-request, but will be ignored by the SSP. + Object.keys(ORTB_VIDEO_PARAMS).forEach(paramName => { + if (videoParams.hasOwnProperty(paramName)) { + if (ORTB_VIDEO_PARAMS[paramName](videoParams[paramName])) { + bidRequest.mediaTypes.video[paramName] = videoParams[paramName]; + } else { + delete bidRequest.mediaTypes.video[paramName]; + utils.logWarn(`${LOG_PREFIX} The OpenRTB video param ${paramName} has been skipped due to misformating. Please refer to OpenRTB 2.5 spec.`); + } + } + }); +} + +function _renderer(bid) { + bid.renderer.push(() => { + if (typeof window.ADAGIO.outstreamPlayer === 'function') { + window.ADAGIO.outstreamPlayer(bid); + } else { + utils.logError(`${LOG_PREFIX} Adagio outstream player is not defined`); + } + }); +} + export const spec = { code: BIDDER_CODE, gvlid: GVLID, @@ -600,7 +701,7 @@ export const spec = { ...params, adUnitElementId, environment - } + }; const debugData = () => ({ action: 'pb-dbg', @@ -631,7 +732,7 @@ export const spec = { // Store adUnits config. // If an adUnitCode has already been stored, it will be replaced. w.ADAGIO = w.ADAGIO || {}; - w.ADAGIO.pbjsAdUnits = w.ADAGIO.pbjsAdUnits.filter((adUnit) => adUnit.code !== adUnitCode) + w.ADAGIO.pbjsAdUnits = w.ADAGIO.pbjsAdUnits.filter((adUnit) => adUnit.code !== adUnitCode); w.ADAGIO.pbjsAdUnits.push({ code: adUnitCode, mediaTypes: mediaTypes || {}, @@ -667,6 +768,11 @@ export const spec = { const eids = _getEids(validBidRequests[0]) || []; const adUnits = utils._map(validBidRequests, (bidRequest) => { bidRequest.features = internal.getFeatures(bidRequest, bidderRequest); + + if (utils.deepAccess(bidRequest, 'mediaTypes.video')) { + _buildVideoBidRequest(bidRequest); + } + return bidRequest; }); @@ -674,7 +780,7 @@ export const spec = { const groupedAdUnits = adUnits.reduce((groupedAdUnits, adUnit) => { adUnit.params.organizationId = adUnit.params.organizationId.toString(); - groupedAdUnits[adUnit.params.organizationId] = groupedAdUnits[adUnit.params.organizationId] || [] + groupedAdUnits[adUnit.params.organizationId] = groupedAdUnits[adUnit.params.organizationId] || []; groupedAdUnits[adUnit.params.organizationId].push(adUnit); return groupedAdUnits; @@ -709,7 +815,7 @@ export const spec = { options: { contentType: 'text/plain' } - } + }; }); return requests; @@ -730,7 +836,30 @@ export const spec = { if (response.bids) { response.bids.forEach(bidObj => { const bidReq = (find(bidRequest.data.adUnits, bid => bid.bidId === bidObj.requestId)); + if (bidReq) { + if (bidObj.mediaType === VIDEO) { + const mediaTypeContext = utils.deepAccess(bidReq, 'mediaTypes.video.context'); + // Adagio SSP returns a `vastXml` only. No `vastUrl` nor `videoCacheKey`. + if (!bidObj.vastUrl && bidObj.vastXml) { + bidObj.vastUrl = 'data:text/xml;charset=utf-8;base64,' + btoa(bidObj.vastXml.replace(/\\"/g, '"')); + } + + if (mediaTypeContext === OUTSTREAM) { + bidObj.renderer = Renderer.install({ + id: bidObj.requestId, + adUnitCode: bidObj.adUnitCode, + url: bidObj.urlRenderer || RENDERER_URL, + config: { + ...utils.deepAccess(bidReq, 'mediaTypes.video'), + ...utils.deepAccess(bidObj, 'outstream', {}) + } + }); + + bidObj.renderer.setRender(_renderer); + } + } + bidObj.site = bidReq.params.site; bidObj.placement = bidReq.params.placement; bidObj.pagetype = bidReq.params.pagetype; diff --git a/modules/adagioBidAdapter.md b/modules/adagioBidAdapter.md index b34cc3fe37a..c55a24f1115 100644 --- a/modules/adagioBidAdapter.md +++ b/modules/adagioBidAdapter.md @@ -22,10 +22,10 @@ Connects to Adagio demand source to fetch bids. bids: [{ bidder: 'adagio', // Required params: { - organizationId: '0', // Required - Organization ID provided by Adagio. - site: 'news-of-the-day', // Required - Site Name provided by Adagio. - placement: 'ban_atf', // Required. Refers to the placement of an adunit in a page. Must not contain any information about the type of device. Other example: `mpu_btf'. - adUnitElementId: 'dfp_banniere_atf', // Required - AdUnit element id. Refers to the adunit id in a page. Usually equals to the adunit code above. + organizationId: '1002', // Required - Organization ID provided by Adagio. + site: 'adagio-io', // Required - Site Name provided by Adagio. + placement: 'in_article', // Required. Refers to the placement of an adunit in a page. Must not contain any information about the type of device. Other example: `mpu_btf'. + adUnitElementId: 'article_outstream', // Required - AdUnit element id. Refers to the adunit id in a page. Usually equals to the adunit code above. // The following params are limited to 30 characters, // and can only contain the following characters: @@ -37,7 +37,54 @@ Connects to Adagio demand source to fetch bids. environment: 'mobile', // Recommended. Environment where the page is displayed. category: 'sport', // Recommended. Category of the content displayed in the page. subcategory: 'handball', // Optional. Subcategory of the content displayed in the page. - postBid: false // Optional. Use it in case of Post-bid integration only. + postBid: false, // Optional. Use it in case of Post-bid integration only. + // Optional debug mode, used to get a bid response with expected cpm. + debug: { + enabled: true, + cpm: 3.00 // default to 1.00 + } + } + }] + }, + { + code: 'article_outstream', + mediaTypes: { + video: { + context: 'outstream', + playerSize: [640, 480], + mimes: ['video/mp4'], + skip: 1 + // Other OpenRTB 2.5 video options… + } + }, + bids: [{ + bidder: 'adagio', // Required + params: { + organizationId: '1002', // Required - Organization ID provided by Adagio. + site: 'adagio-io', // Required - Site Name provided by Adagio. + placement: 'in_article', // Required. Refers to the placement of an adunit in a page. Must not contain any information about the type of device. Other example: `mpu_btf'. + adUnitElementId: 'article_outstream', // Required - AdUnit element id. Refers to the adunit id in a page. Usually equals to the adunit code above. + + // The following params are limited to 30 characters, + // and can only contain the following characters: + // - alphanumeric (A-Z+a-z+0-9, case-insensitive) + // - dashes `-` + // - underscores `_` + // Also, each param can have at most 50 unique active values (case-insensitive). + pagetype: 'article', // Highly recommended. The pagetype describes what kind of content will be present in the page. + environment: 'mobile', // Recommended. Environment where the page is displayed. + category: 'sport', // Recommended. Category of the content displayed in the page. + subcategory: 'handball', // Optional. Subcategory of the content displayed in the page. + postBid: false, // Optional. Use it in case of Post-bid integration only. + video: { + skip: 0 + // OpenRTB 2.5 video options defined here override ones defined in mediaTypes. + }, + // Optional debug mode, used to get a bid response with expected cpm. + debug: { + enabled: true, + cpm: 3.00 // default to 1.00 + } } }] } @@ -88,5 +135,4 @@ Connects to Adagio demand source to fetch bids. ] } } - ``` diff --git a/test/spec/modules/adagioBidAdapter_spec.js b/test/spec/modules/adagioBidAdapter_spec.js index 86fb2e7cbd3..2cf97a1129b 100644 --- a/test/spec/modules/adagioBidAdapter_spec.js +++ b/test/spec/modules/adagioBidAdapter_spec.js @@ -1,6 +1,16 @@ import find from 'core-js-pure/features/array/find.js'; import { expect } from 'chai'; -import { _features, internal as adagio, adagioScriptFromLocalStorageCb, getAdagioScript, storage, spec, ENDPOINT, VERSION } from '../../../modules/adagioBidAdapter.js'; +import { + _features, + internal as adagio, + adagioScriptFromLocalStorageCb, + getAdagioScript, + storage, + spec, + ENDPOINT, + VERSION, + RENDERER_URL +} from '../../../modules/adagioBidAdapter.js'; import { loadExternalScript } from '../../../src/adloader.js'; import * as utils from '../../../src/utils.js'; import { config } from 'src/config.js'; @@ -107,7 +117,7 @@ describe('Adagio bid adapter', () => { adagioMock = sinon.mock(adagio); utilsMock = sinon.mock(utils); - sandbox = sinon.sandbox.create(); + sandbox = sinon.createSandbox(); }); afterEach(() => { @@ -254,7 +264,7 @@ describe('Adagio bid adapter', () => { // replace by the values defined in beforeEach window.top.ADAGIO = { ...window.ADAGIO - } + }; spec.isBidRequestValid(bid01); spec.isBidRequestValid(bid02); @@ -384,6 +394,81 @@ describe('Adagio bid adapter', () => { expect(requests[0].data.adUnits[0].features.url).to.not.exist; }); + describe('With video mediatype', function() { + context('Outstream video', function() { + it('should logWarn if user does not set renderer.backupOnly: true', function() { + sandbox.spy(utils, 'logWarn'); + const bid01 = new BidRequestBuilder({ + adUnitCode: 'adunit-code-01', + mediaTypes: { + banner: { sizes: [[300, 250]] }, + video: { + context: 'outstream', + playerSize: [[300, 250]], + renderer: { + url: 'https://url.tld', + render: () => true + } + } + }, + }).withParams().build(); + const bidderRequest = new BidderRequestBuilder().build(); + const request = spec.buildRequests([bid01], bidderRequest)[0]; + + expect(request.data.adUnits[0].mediaTypes.video.playerName).to.equal('other'); + sinon.assert.calledWith(utils.logWarn, 'Adagio: renderer.backupOnly has not been set. Adagio recommends to use its own player to get expected behavior.'); + }); + }); + + it('Update mediaTypes.video with OpenRTB options. Validate and sanitize whitelisted OpenRTB', function() { + sandbox.spy(utils, 'logWarn'); + const bid01 = new BidRequestBuilder({ + adUnitCode: 'adunit-code-01', + mediaTypes: { + banner: { sizes: [[300, 250]] }, + video: { + context: 'outstream', + playerSize: [[300, 250]], + mimes: ['video/mp4'], + api: 5, // will be removed because invalid + playbackmethod: [7], // will be removed because invalid + } + }, + }).withParams({ + // options in video, will overide + video: { + skip: 1, + skipafter: 4, + minduration: 10, + maxduration: 30, + placement: [3], + protocols: [8] + } + }).build(); + + const bidderRequest = new BidderRequestBuilder().build(); + const expected = { + context: 'outstream', + playerSize: [[300, 250]], + playerName: 'adagio', + mimes: ['video/mp4'], + skip: 1, + skipafter: 4, + minduration: 10, + maxduration: 30, + placement: [3], + protocols: [8], + w: 300, + h: 250 + }; + + const requests = spec.buildRequests([bid01], bidderRequest); + expect(requests).to.have.lengthOf(1); + expect(requests[0].data.adUnits[0].mediaTypes.video).to.deep.equal(expected); + sinon.assert.calledTwice(utils.logWarn); + }); + }); + describe('with sChain', function() { const schain = { ver: '1.0', @@ -550,7 +635,7 @@ describe('Adagio bid adapter', () => { describe('with USPrivacy', function() { const bid01 = new BidRequestBuilder().withParams().build(); - const consent = 'Y11N' + const consent = 'Y11N'; it('should send the USPrivacy "ccpa.uspConsent" in the request', function () { const bidderRequest = new BidderRequestBuilder({ @@ -579,7 +664,7 @@ describe('Adagio bid adapter', () => { const userId = { sharedid: {id: '01EAJWWNEPN3CYMM5N8M5VXY22', third: '01EAJWWNEPN3CYMM5N8M5VXY22'}, unsuported: '666' - } + }; it('should send "user.eids" in the request for Prebid.js supported modules only', function() { const bid01 = new BidRequestBuilder({ @@ -601,11 +686,11 @@ describe('Adagio bid adapter', () => { id: '01EAJWWNEPN3CYMM5N8M5VXY22' } ] - }] + }]; - expect(requests[0].data.user.eids).to.have.lengthOf(1) - expect(requests[0].data.user.eids).to.deep.equal(expected) - }) + expect(requests[0].data.user.eids).to.have.lengthOf(1); + expect(requests[0].data.user.eids).to.deep.equal(expected); + }); it('should send an empty "user.eids" array in the request if userId module is unsupported', function() { const bid01 = new BidRequestBuilder({ @@ -618,9 +703,9 @@ describe('Adagio bid adapter', () => { const requests = spec.buildRequests([bid01], bidderRequest); - expect(requests[0].data.user.eids).to.be.empty - }) - }) + expect(requests[0].data.user.eids).to.be.empty; + }); + }); }); describe('interpretResponse()', function() { @@ -732,6 +817,37 @@ describe('Adagio bid adapter', () => { utilsMock.verify(); }); + + describe('Response with video outstream', () => { + const bidRequestWithOutstream = utils.deepClone(bidRequest); + bidRequestWithOutstream.data.adUnits[0].mediaTypes.video = { + context: 'outstream', + playerSize: [[300, 250]], + mimes: ['video/mp4'], + skip: true + }; + + const serverResponseWithOutstream = utils.deepClone(serverResponse); + serverResponseWithOutstream.body.bids[0].vastXml = ''; + serverResponseWithOutstream.body.bids[0].mediaType = 'video'; + serverResponseWithOutstream.body.bids[0].outstream = { + bvwUrl: 'https://foo.baz', + impUrl: 'https://foo.bar' + }; + + it('should set a renderer in video outstream context', function() { + const bidResponse = spec.interpretResponse(serverResponseWithOutstream, bidRequestWithOutstream)[0]; + expect(bidResponse).to.have.any.keys('outstream', 'renderer', 'mediaType'); + expect(bidResponse.renderer).to.be.a('object'); + expect(bidResponse.renderer.url).to.equal(RENDERER_URL); + expect(bidResponse.renderer.config.bvwUrl).to.be.ok; + expect(bidResponse.renderer.config.impUrl).to.be.ok; + expect(bidResponse.renderer.loaded).to.not.be.ok; + expect(bidResponse.width).to.equal(300); + expect(bidResponse.height).to.equal(250); + expect(bidResponse.vastUrl).to.match(/^data:text\/xml;/) + }); + }); }); describe('getUserSyncs()', function() { @@ -1195,7 +1311,7 @@ describe('Adagio bid adapter', () => { expect(loadExternalScript.called).to.be.false; expect(localStorage.getItem(ADAGIO_LOCALSTORAGE_KEY)).to.be.null; - }) + }); }); it('should verify valid hash with valid script', function () { From 5552473087d8e80d953ada7710206c9e1387252e Mon Sep 17 00:00:00 2001 From: Telaria Engineering <36203956+telariaEng@users.noreply.github.com> Date: Fri, 4 Dec 2020 03:10:45 -0800 Subject: [PATCH 076/103] Not using utils.isEmpty on non objects (#6036) * Added telaria bid adapter * more documentation * Added more test cases. And improved some code in the adapter * Removed the check for optional params, they are handled in the server. Also updated certain param names used in the test spec. * added some spaces to fix CircleCI tests * added some spaces to fix CircleCI tests * fixed code indentation in /spec/AnalyticsAdapter_spec.js which causing the CircleCI tests to fail. * Reverted the changes * merged with prebid master. * creative Id is required when we build a response but our server doesn't always have the crid, so using a sentinel value when we don't have the crid. * - removed an un used method - Removed the package-lock file. * merging to master * updated telaria bid adapter to use player size provided by the bid.mediaTypes.video.playerSize instead of bid.sizes. https://github.com/prebid/Prebid.js/issues/3331 * - removed the requirement for having player size - updated the test spec to reflect the above change - removed changes to the package-lock.json file. * added a param to the ad call url to let us know that the request is coming via hb. * to lower casing the bidder code. * Merge branch 'master' of https://github.com/prebid/Prebid.js # Conflicts: # modules/telariaBidAdapter.js Added GDPR support * Sending the gdpr & gdpr consent string only if they're defined * - Updated the test ad unit to use 'telaria' as the bidder code. - Added an example URL. * using the bidder code constant * - Implemented the 'onTimeout' callback to fire a pixel when there's a timeout. - Added the ability to serialize an schain object according to the description provided here: https://github.com/InteractiveAdvertisingBureau/openrtb/blob/master/supplychainobject.md * some mods to the schain tag generation * - added tests for schain param checking. * - fixed a malformed url for timeouts * - Removed a trailing ',' while generating a schain param. * - Using the schain object from validBidRequest if present. Reverting to checking if params has it if not. * - reverting changes to merge with master * - Resolving merge issues * - some formatting changes * using val !== '' instead of utils.isEmpty(val) * Checking for undefined in the getEncodedValIfNotEmpty method Co-authored-by: Vinay Prasad Co-authored-by: Vinay Prasad --- modules/telariaBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/telariaBidAdapter.js b/modules/telariaBidAdapter.js index acc20f6b183..71651b5af94 100644 --- a/modules/telariaBidAdapter.js +++ b/modules/telariaBidAdapter.js @@ -124,7 +124,7 @@ function getDefaultSrcPageUrl() { } function getEncodedValIfNotEmpty(val) { - return !utils.isEmpty(val) ? encodeURIComponent(val) : ''; + return (val !== '' && val !== undefined) ? encodeURIComponent(val) : ''; } /** From 4b0b82f629f952716cb065fd11b9cfb6e8497c1d Mon Sep 17 00:00:00 2001 From: iskmerof Date: Fri, 4 Dec 2020 06:47:55 -0500 Subject: [PATCH 077/103] Update adkernelBidAdapter.js for client alias (#6055) * Update adkernelBidAdapter.js for client alias * Update test unit to match new alias bidder count --- modules/adkernelBidAdapter.js | 2 +- test/spec/modules/adkernelBidAdapter_spec.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/adkernelBidAdapter.js b/modules/adkernelBidAdapter.js index 29990ef1c44..c34902eda46 100644 --- a/modules/adkernelBidAdapter.js +++ b/modules/adkernelBidAdapter.js @@ -52,7 +52,7 @@ const NATIVE_INDEX = NATIVE_MODEL.reduce((acc, val, idx) => { export const spec = { code: 'adkernel', - aliases: ['headbidding', 'adsolut', 'oftmediahb', 'audiencemedia', 'waardex_ak', 'roqoon', 'andbeyond', 'adbite'], + aliases: ['headbidding', 'adsolut', 'oftmediahb', 'audiencemedia', 'waardex_ak', 'roqoon', 'andbeyond', 'adbite', 'houseofpubs'], supportedMediaTypes: [BANNER, VIDEO, NATIVE], /** diff --git a/test/spec/modules/adkernelBidAdapter_spec.js b/test/spec/modules/adkernelBidAdapter_spec.js index 70789c4b933..4d3dca7f344 100644 --- a/test/spec/modules/adkernelBidAdapter_spec.js +++ b/test/spec/modules/adkernelBidAdapter_spec.js @@ -556,7 +556,7 @@ describe('Adkernel adapter', function () { describe('adapter configuration', () => { it('should have aliases', () => { - expect(spec.aliases).to.have.lengthOf(8); + expect(spec.aliases).to.have.lengthOf(9); }); }); From 8107da60820c5b5305b391c025d076cff35bc232 Mon Sep 17 00:00:00 2001 From: Rich Audience Date: Fri, 4 Dec 2020 14:08:49 +0100 Subject: [PATCH 078/103] Add GVLID RichaudienceAdapter (#6071) * Add GVLID RichaudienceAdapter * Update package-lock.json Co-authored-by: sgimenez --- modules/richaudienceBidAdapter.js | 1 + .../modules/richaudienceBidAdapter_spec.js | 27 +++++++++++-------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/modules/richaudienceBidAdapter.js b/modules/richaudienceBidAdapter.js index 3b899e2179d..a6b4202fc91 100755 --- a/modules/richaudienceBidAdapter.js +++ b/modules/richaudienceBidAdapter.js @@ -9,6 +9,7 @@ let REFERER = ''; export const spec = { code: BIDDER_CODE, + gvlid: 108, aliases: ['ra'], supportedMediaTypes: [BANNER, VIDEO], diff --git a/test/spec/modules/richaudienceBidAdapter_spec.js b/test/spec/modules/richaudienceBidAdapter_spec.js index bdf50f2d7f5..90723fb863f 100644 --- a/test/spec/modules/richaudienceBidAdapter_spec.js +++ b/test/spec/modules/richaudienceBidAdapter_spec.js @@ -353,20 +353,25 @@ describe('Richaudience adapter tests', function () { cmpApi: 'iab', timeout: 5000, allowAuctionWithoutConsent: true + }, + userSync: { + userIds: [{ + name: 'id5Id', + params: { + partner: 173, // change to the Partner Number you received from ID5 + pd: 'MT1iNTBjY...' // optional, see table below for a link to how to generate this + }, + storage: { + type: 'html5', // "html5" is the required storage type + name: 'id5id', // "id5id" is the required storage name + expires: 90, // storage lasts for 90 days + refreshInSeconds: 8 * 3600 // refresh ID every 8 hours to ensure it's fresh + } + }], + auctionDelay: 50 // 50ms maximum auction delay, applies to all userId modules } }); it('Verify build id5', function () { - DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.id5id = { uid: 'id5-user-id' }; - - var request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - var requestContent = JSON.parse(request[0].data); - - expect(requestContent.user).to.deep.equal([{ - 'userId': 'id5-user-id', - 'source': 'id5-sync.com' - }]); - var request; DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; DEFAULT_PARAMS_WO_OPTIONAL[0].userId.id5id = { uid: 1 }; From 8ea3f3b2727439464858bcfcbbc077d863e45c58 Mon Sep 17 00:00:00 2001 From: susyt Date: Fri, 4 Dec 2020 08:45:09 -0800 Subject: [PATCH 079/103] GumGum: sets mediaType of bidRequest depending on product id (#6066) * adds support for zone and pubId params * adds support for iriscat field * sets mediatype depending on product id * Update doc for mediaType needed for video products --- modules/gumgumBidAdapter.js | 5 ++-- modules/gumgumBidAdapter.md | 27 ++++++++++++++++- test/spec/modules/gumgumBidAdapter_spec.js | 34 +++++++++++++++------- 3 files changed, 53 insertions(+), 13 deletions(-) diff --git a/modules/gumgumBidAdapter.js b/modules/gumgumBidAdapter.js index 3206b7e1727..2cb5ce61064 100644 --- a/modules/gumgumBidAdapter.js +++ b/modules/gumgumBidAdapter.js @@ -403,6 +403,7 @@ function interpretResponse (serverResponse, bidRequest) { } = Object.assign(defaultResponse, serverResponseBody) let data = bidRequest.data || {} let product = data.pi + let mediaType = (product === 6 || product === 7) ? VIDEO : BANNER let isTestUnit = (product === 3 && data.si === 9) let sizes = utils.parseSizesInput(bidRequest.sizes) let [width, height] = sizes[0].split('x') @@ -424,9 +425,9 @@ function interpretResponse (serverResponse, bidRequest) { bidResponses.push({ // dealId: DEAL_ID, // referrer: REFERER, - ...(product === 7 && { vastXml: markup, mediaType: VIDEO }), ad: wrapper ? getWrapperCode(wrapper, Object.assign({}, serverResponseBody, { bidRequest })) : markup, - ...(product === 6 && {ad: markup}), + ...(mediaType === VIDEO && {ad: markup, vastXml: markup}), + mediaType, cpm: isTestUnit ? 0.1 : cpm, creativeId, currency: cur || 'USD', diff --git a/modules/gumgumBidAdapter.md b/modules/gumgumBidAdapter.md index f47666e9628..7b4f0c98ea7 100644 --- a/modules/gumgumBidAdapter.md +++ b/modules/gumgumBidAdapter.md @@ -8,7 +8,10 @@ Maintainer: engineering@gumgum.com # Description -GumGum adapter for Prebid.js 1.0 +GumGum adapter for Prebid.js +Please note that both video and in-video products require a mediaType of video. +All other products (in-screen, slot, native) should have a mediaType of banner. + # Test Parameters ``` @@ -16,6 +19,11 @@ var adUnits = [ { code: 'test-div', sizes: [[300, 250]], + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, bids: [ { bidder: 'gumgum', @@ -28,6 +36,11 @@ var adUnits = [ },{ code: 'test-div', sizes: [[300, 50]], + mediaTypes: { + banner: { + sizes: [[1, 1]], + } + }, bids: [ { bidder: 'gumgum', @@ -40,6 +53,18 @@ var adUnits = [ },{ code: 'test-div', sizes: [[300, 50]], + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480], + minduration: 1, + maxduration: 2, + linearity: 2, + startdelay: 1, + placement: 1, + protocols: [1, 2] + } + } bids: [ { bidder: 'gumgum', diff --git a/test/spec/modules/gumgumBidAdapter_spec.js b/test/spec/modules/gumgumBidAdapter_spec.js index 701ce9a7e81..52a3a21db4e 100644 --- a/test/spec/modules/gumgumBidAdapter_spec.js +++ b/test/spec/modules/gumgumBidAdapter_spec.js @@ -1,6 +1,7 @@ import { expect } from 'chai'; import { newBidder } from 'src/adapters/bidderFactory.js'; import { spec } from 'modules/gumgumBidAdapter.js'; +import { BANNER, VIDEO } from 'src/mediaTypes.js'; const ENDPOINT = 'https://g2.gumgum.com/hbid/imp'; const JCSI = { t: 0, rq: 8, pbv: '$prebid.version$' } @@ -462,16 +463,15 @@ describe('gumgumAdapter', function () { pi: 3 } let expectedResponse = { - 'ad': '

I am an ad

', - 'cpm': 0, - 'creativeId': 29593, - 'currency': 'USD', - 'height': '250', - 'netRevenue': true, - 'requestId': 12345, - 'width': '300', - // dealId: DEAL_ID, - // referrer: REFERER, + ad: '

I am an ad

', + cpm: 0, + creativeId: 29593, + currency: 'USD', + height: '250', + netRevenue: true, + requestId: 12345, + width: '300', + mediaType: BANNER, ttl: 60 }; @@ -552,6 +552,20 @@ describe('gumgumAdapter', function () { const decodedResponse = JSON.parse(atob(bidResponse)); expect(decodedResponse.jcsi).to.eql(JCSI); }); + + it('sets the correct mediaType depending on product', function () { + const bannerBidResponse = spec.interpretResponse({ body: serverResponse }, bidRequest)[0]; + const invideoBidResponse = spec.interpretResponse({ body: serverResponse }, { ...bidRequest, data: { pi: 6 } })[0]; + const videoBidResponse = spec.interpretResponse({ body: serverResponse }, { ...bidRequest, data: { pi: 7 } })[0]; + expect(bannerBidResponse.mediaType).to.equal(BANNER); + expect(invideoBidResponse.mediaType).to.equal(VIDEO); + expect(videoBidResponse.mediaType).to.equal(VIDEO); + }); + + it('sets a vastXml property if mediaType is video', function () { + const videoBidResponse = spec.interpretResponse({ body: serverResponse }, { ...bidRequest, data: { pi: 7 } })[0]; + expect(videoBidResponse.vastXml).to.exist; + }); }) describe('getUserSyncs', function () { const syncOptions = { From 93536f0e5aa56ec8b3284feafeee698eaa974a74 Mon Sep 17 00:00:00 2001 From: Meng <5110935+edmonl@users.noreply.github.com> Date: Fri, 4 Dec 2020 13:29:42 -0500 Subject: [PATCH 080/103] pubgenius: remove video cache key to be future-proof (#6081) --- modules/pubgeniusBidAdapter.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/modules/pubgeniusBidAdapter.js b/modules/pubgeniusBidAdapter.js index 88e85a4fd7a..5c750e66c25 100644 --- a/modules/pubgeniusBidAdapter.js +++ b/modules/pubgeniusBidAdapter.js @@ -253,10 +253,6 @@ function interpretBid(bid) { bidResponse.vastXml = bid.adm; } - if (pbadapter.cacheKey) { - bidResponse.videoCacheKey = pbadapter.cacheKey; - } - bidResponse.mediaType = VIDEO; break; default: // banner by default From b9a4cc6eb780e79e4f5775d3519af8ad38f23d0a Mon Sep 17 00:00:00 2001 From: jsnellbaker <31102355+jsnellbaker@users.noreply.github.com> Date: Mon, 7 Dec 2020 10:36:18 -0500 Subject: [PATCH 081/103] fix pubgenius unit test (#6090) --- test/spec/modules/pubgeniusBidAdapter_spec.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/spec/modules/pubgeniusBidAdapter_spec.js b/test/spec/modules/pubgeniusBidAdapter_spec.js index c0b05510707..382199dcffc 100644 --- a/test/spec/modules/pubgeniusBidAdapter_spec.js +++ b/test/spec/modules/pubgeniusBidAdapter_spec.js @@ -472,7 +472,6 @@ describe('pubGENIUS adapter', () => { ...expectedBidResponse, vastUrl: 'http://vasturl/cache?id=x', vastXml: 'fake_creative', - videoCacheKey: 'x', mediaType: VIDEO, }; From 5f6dab3008d4cca306b6d27a1d27937723fc2b89 Mon Sep 17 00:00:00 2001 From: Harshad Mane Date: Mon, 7 Dec 2020 07:39:25 -0800 Subject: [PATCH 082/103] 6012: Fix for passing US Privacy string in buildVideoUrl and buildAdpodVideoUrl (#6075) * added support for pubcommon, digitrust, id5id * added support for IdentityLink * changed the source for id5 * added unit test cases * changed source param for identityLink * pass USP consent string in dfpVideoUrl and dfpAdpodVideoUrl * added test cases --- modules/dfpAdServerVideo.js | 7 ++++ test/spec/modules/dfpAdServerVideo_spec.js | 43 ++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/modules/dfpAdServerVideo.js b/modules/dfpAdServerVideo.js index 554c44aa708..13677c90bae 100644 --- a/modules/dfpAdServerVideo.js +++ b/modules/dfpAdServerVideo.js @@ -8,6 +8,7 @@ import { deepAccess, isEmpty, logError, parseSizesInput, formatQS, parseUrl, bui import { config } from '../src/config.js'; import { getHook, submodule } from '../src/hook.js'; import { auctionManager } from '../src/auctionManager.js'; +import { uspDataHandler } from '../src/adapterManager.js'; import events from '../src/events.js'; import CONSTANTS from '../src/constants.json'; @@ -100,6 +101,9 @@ export function buildDfpVideoUrl(options) { const descriptionUrl = getDescriptionUrl(bid, options, 'params'); if (descriptionUrl) { queryParams.description_url = descriptionUrl; } + const uspConsent = uspDataHandler.getConsentData(); + if (uspConsent) { queryParams.us_privacy = uspConsent; } + return buildUrl({ protocol: 'https', host: 'securepubads.g.doubleclick.net', @@ -183,6 +187,9 @@ export function buildAdpodVideoUrl({code, params, callback} = {}) { { cust_params: encodedCustomParams } ); + const uspConsent = uspDataHandler.getConsentData(); + if (uspConsent) { queryParams.us_privacy = uspConsent; } + const masterTag = buildUrl({ protocol: 'https', host: 'securepubads.g.doubleclick.net', diff --git a/test/spec/modules/dfpAdServerVideo_spec.js b/test/spec/modules/dfpAdServerVideo_spec.js index c0ecb9cad5e..ed9c968cfa2 100644 --- a/test/spec/modules/dfpAdServerVideo_spec.js +++ b/test/spec/modules/dfpAdServerVideo_spec.js @@ -7,6 +7,7 @@ import * as utils from 'src/utils.js'; import { config } from 'src/config.js'; import { targeting } from 'src/targeting.js'; import { auctionManager } from 'src/auctionManager.js'; +import { uspDataHandler } from 'src/adapterManager.js'; import * as adpod from 'modules/adpod.js'; import { server } from 'test/mocks/xhr.js'; @@ -115,6 +116,44 @@ describe('The DFP video support module', function () { expect(customParams).to.have.property('hb_cache_id', bid.videoCacheKey); }); + it('should include the us_privacy key when USP Consent is available', function () { + let uspDataHandlerStub = sinon.stub(uspDataHandler, 'getConsentData'); + uspDataHandlerStub.returns('1YYY'); + + const bidCopy = utils.deepClone(bid); + bidCopy.adserverTargeting = Object.assign(bidCopy.adserverTargeting, { + hb_adid: 'ad_id', + }); + + const url = parse(buildDfpVideoUrl({ + adUnit: adUnit, + bid: bidCopy, + params: { + 'iu': 'my/adUnit' + } + })); + const queryObject = utils.parseQS(url.query); + expect(queryObject.us_privacy).to.equal('1YYY'); + uspDataHandlerStub.restore(); + }); + + it('should not include the us_privacy key when USP Consent is not available', function () { + const bidCopy = utils.deepClone(bid); + bidCopy.adserverTargeting = Object.assign(bidCopy.adserverTargeting, { + hb_adid: 'ad_id', + }); + + const url = parse(buildDfpVideoUrl({ + adUnit: adUnit, + bid: bidCopy, + params: { + 'iu': 'my/adUnit' + } + })); + const queryObject = utils.parseQS(url.query); + expect(queryObject.us_privacy).to.equal(undefined); + }); + describe('special targeting unit test', function () { const allTargetingData = { 'hb_format': 'video', @@ -350,6 +389,8 @@ describe('The DFP video support module', function () { it('should return masterTag url', function() { amStub.returns(getBidsReceived()); + let uspDataHandlerStub = sinon.stub(uspDataHandler, 'getConsentData'); + uspDataHandlerStub.returns('1YYY'); let url; parse(buildAdpodVideoUrl({ code: 'adUnitCode-1', @@ -380,10 +421,12 @@ describe('The DFP video support module', function () { expect(queryParams).to.have.property('unviewed_position_start', '1'); expect(queryParams).to.have.property('url'); expect(queryParams).to.have.property('cust_params'); + expect(queryParams).to.have.property('us_privacy', '1YYY'); const custParams = utils.parseQS(decodeURIComponent(queryParams.cust_params)); expect(custParams).to.have.property('hb_cache_id', '123'); expect(custParams).to.have.property('hb_pb_cat_dur', '15.00_395_15s,15.00_406_30s,10.00_395_15s'); + uspDataHandlerStub.restore(); } }); From dbd0d84e30beb1547f35663e0c30d6efc45b60b0 Mon Sep 17 00:00:00 2001 From: Reinout Stevens Date: Mon, 7 Dec 2020 16:40:06 +0100 Subject: [PATCH 083/103] fix prebid server playerwidth and height (#6073) Co-authored-by: Reinout Stevens --- modules/prebidServerBidAdapter/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index 7c7962781d2..d90572d1093 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -750,8 +750,8 @@ const OPEN_RTB_PROTOCOL = { if (utils.deepAccess(bid, 'ext.prebid.type') === VIDEO) { bidObject.mediaType = VIDEO; let sizes = bidRequest.sizes && bidRequest.sizes[0]; - bidObject.playerHeight = sizes[0]; - bidObject.playerWidth = sizes[1]; + bidObject.playerWidth = sizes[0]; + bidObject.playerHeight = sizes[1]; // try to get cache values from 'response.ext.prebid.cache.js' // else try 'bid.ext.prebid.targeting' as fallback From 93f393abf070ff83ce2cc9cefb376864816a1feb Mon Sep 17 00:00:00 2001 From: Paul de Rosanbo Date: Mon, 7 Dec 2020 16:40:51 +0100 Subject: [PATCH 084/103] Fix iframe __tcfapi arguments (#6058) * Fix __tcfapi declaration in iframe * Add tests for cmp declaration in iframe --- modules/consentManagement.js | 58 ++++++++++++++------- test/spec/modules/consentManagement_spec.js | 18 +++++++ 2 files changed, 58 insertions(+), 18 deletions(-) diff --git a/modules/consentManagement.js b/modules/consentManagement.js index 1060fdb5cc5..67af2baf959 100644 --- a/modules/consentManagement.js +++ b/modules/consentManagement.js @@ -199,29 +199,51 @@ function lookupIabConsent(cmpSuccess, cmpError, hookConfig) { function callCmpWhileInIframe(commandName, cmpFrame, moduleCallback) { let apiName = (cmpVersion === 2) ? '__tcfapi' : '__cmp'; + let callId = Math.random() + ''; + let callName = `${apiName}Call`; + /* Setup up a __cmp function to do the postMessage and stash the callback. This function behaves (from the caller's perspective identicially to the in-frame __cmp call */ - window[apiName] = function (cmd, arg, callback) { - let callId = Math.random() + ''; - let callName = `${apiName}Call`; - let msg = { - [callName]: { - command: cmd, - parameter: arg, - callId: callId - } - }; - if (cmpVersion !== 1) msg[callName].version = cmpVersion; + if (cmpVersion === 2) { + window[apiName] = function (cmd, cmpVersion, callback, arg) { + let msg = { + [callName]: { + command: cmd, + version: cmpVersion, + parameter: arg, + callId: callId + } + }; - cmpCallbacks[callId] = callback; - cmpFrame.postMessage(msg, '*'); - } + cmpCallbacks[callId] = callback; + cmpFrame.postMessage(msg, '*'); + } - /** when we get the return message, call the stashed callback */ - window.addEventListener('message', readPostMessageResponse, false); + /** when we get the return message, call the stashed callback */ + window.addEventListener('message', readPostMessageResponse, false); - // call CMP - window[apiName](commandName, undefined, moduleCallback); + // call CMP + window[apiName](commandName, cmpVersion, moduleCallback); + } else { + window[apiName] = function (cmd, arg, callback) { + let msg = { + [callName]: { + command: cmd, + parameter: arg, + callId: callId + } + }; + + cmpCallbacks[callId] = callback; + cmpFrame.postMessage(msg, '*'); + } + + /** when we get the return message, call the stashed callback */ + window.addEventListener('message', readPostMessageResponse, false); + + // call CMP + window[apiName](commandName, undefined, moduleCallback); + } function readPostMessageResponse(event) { let cmpDataPkgName = `${apiName}Return`; diff --git a/test/spec/modules/consentManagement_spec.js b/test/spec/modules/consentManagement_spec.js index cf5c578502f..5e9b0f07f46 100644 --- a/test/spec/modules/consentManagement_spec.js +++ b/test/spec/modules/consentManagement_spec.js @@ -537,6 +537,15 @@ describe('consentManagement', function () { // from CMP window postMessage listener. testIFramedPage('with/JSON response', false, 'encoded_consent_data_via_post_message', 1); testIFramedPage('with/String response', true, 'encoded_consent_data_via_post_message', 1); + + it('should contain correct V1 CMP definition', (done) => { + setConsentConfig(goodConfigWithAllowAuction); + requestBidsHook(() => { + const nbArguments = window.__cmp.toString().split('\n')[0].split(', ').length; + expect(nbArguments).to.equal(3); + done(); + }, {}); + }); }); describe('v2 CMP workflow for iframe pages:', function () { @@ -562,6 +571,15 @@ describe('consentManagement', function () { testIFramedPage('with/JSON response', false, 'abc12345234', 2); testIFramedPage('with/String response', true, 'abc12345234', 2); + + it('should contain correct v2 CMP definition', (done) => { + setConsentConfig(goodConfigWithAllowAuction); + requestBidsHook(() => { + const nbArguments = window.__tcfapi.toString().split('\n')[0].split(', ').length; + expect(nbArguments).to.equal(4); + done(); + }, {}); + }); }); }); From 12d55b951decf7f2786f3cae18d2fa34df76d4c2 Mon Sep 17 00:00:00 2001 From: Samuel Adu Date: Tue, 8 Dec 2020 18:50:33 +0000 Subject: [PATCH 085/103] Feature/vmuid connectid rebrand (#6045) * Key name change from vmuid to vmcid * Change the userId key name to vmconnectid * Support new key in response payload + remain backward compatible. Co-authored-by: slimkrazy --- modules/userId/eids.js | 4 +-- modules/verizonMediaIdSystem.js | 11 +++--- modules/verizonMediaSystemId.md | 4 +-- test/spec/modules/aolBidAdapter_spec.js | 2 +- .../spec/modules/verizonMediaIdSystem_spec.js | 34 +++++++++++++++---- 5 files changed, 39 insertions(+), 16 deletions(-) diff --git a/modules/userId/eids.js b/modules/userId/eids.js index 2c627416341..d714d09962d 100644 --- a/modules/userId/eids.js +++ b/modules/userId/eids.js @@ -158,8 +158,8 @@ const USER_IDS_CONFIG = { atype: 1 }, - // Verizon Media - 'vmuid': { + // Verizon Media ConnectID + 'connectid': { source: 'verizonmedia.com', atype: 1 }, diff --git a/modules/verizonMediaIdSystem.js b/modules/verizonMediaIdSystem.js index 617561765cc..f39909f6666 100644 --- a/modules/verizonMediaIdSystem.js +++ b/modules/verizonMediaIdSystem.js @@ -12,7 +12,7 @@ import * as utils from '../src/utils.js'; const MODULE_NAME = 'verizonMediaId'; const VENDOR_ID = 25; const PLACEHOLDER = '__PIXEL_ID__'; -const VMUID_ENDPOINT = `https://ups.analytics.yahoo.com/ups/${PLACEHOLDER}/fed`; +const VMCID_ENDPOINT = `https://ups.analytics.yahoo.com/ups/${PLACEHOLDER}/fed`; function isEUConsentRequired(consentData) { return !!(consentData && consentData.gdpr && consentData.gdpr.gdprApplies); @@ -33,13 +33,14 @@ export const verizonMediaIdSubmodule = { /** * decode the stored id value for passing to bid requests * @function - * @returns {{vmuid: string} | undefined} + * @returns {{connectid: string} | undefined} */ decode(value) { - return (value && typeof value.vmuid === 'string') ? {vmuid: value.vmuid} : undefined; + return (typeof value === 'object' && (value.connectid || value.vmuid)) + ? {connectid: value.connectid || value.vmuid} : undefined; }, /** - * get the VerizonMedia Id + * Gets the Verizon Media Connect ID * @function * @param {SubmoduleConfig} [config] * @param {ConsentData} [consentData] @@ -83,7 +84,7 @@ export const verizonMediaIdSubmodule = { callback(); } }; - const endpoint = VMUID_ENDPOINT.replace(PLACEHOLDER, params.pixelId); + const endpoint = VMCID_ENDPOINT.replace(PLACEHOLDER, params.pixelId); let url = `${params.endpoint || endpoint}?${utils.formatQS(data)}`; verizonMediaIdSubmodule.getAjaxFn()(url, callbacks, null, {method: 'GET', withCredentials: true}); }; diff --git a/modules/verizonMediaSystemId.md b/modules/verizonMediaSystemId.md index 8d0e0bddaa9..c0d315dc754 100644 --- a/modules/verizonMediaSystemId.md +++ b/modules/verizonMediaSystemId.md @@ -10,9 +10,9 @@ pbjs.setConfig({ userIds: [{ name: 'verizonMediaId', storage: { - name: 'vmuid', + name: 'vmcid', type: 'html5', - expires: 30 + expires: 15 }, params: { pixelId: 58776, diff --git a/test/spec/modules/aolBidAdapter_spec.js b/test/spec/modules/aolBidAdapter_spec.js index 11e1a317b70..8e74e19f420 100644 --- a/test/spec/modules/aolBidAdapter_spec.js +++ b/test/spec/modules/aolBidAdapter_spec.js @@ -93,7 +93,7 @@ describe('AolAdapter', function () { const USER_ID_DATA = { criteoId: SUPPORTED_USER_ID_SOURCES['criteo.com'], - vmuid: SUPPORTED_USER_ID_SOURCES['verizonmedia.com'], + connectid: SUPPORTED_USER_ID_SOURCES['verizonmedia.com'], idl_env: SUPPORTED_USER_ID_SOURCES['liveramp.com'], lipb: { lipbid: SUPPORTED_USER_ID_SOURCES['liveintent.com'], diff --git a/test/spec/modules/verizonMediaIdSystem_spec.js b/test/spec/modules/verizonMediaIdSystem_spec.js index a30be5a2569..c5d743235d6 100644 --- a/test/spec/modules/verizonMediaIdSystem_spec.js +++ b/test/spec/modules/verizonMediaIdSystem_spec.js @@ -165,12 +165,34 @@ describe('Verizon Media ID Submodule', () => { }); describe('decode()', () => { - const VALID_API_RESPONSE = { - vmuid: '1234' - }; - it('should return a newly constructed object with the vmuid property', () => { - expect(verizonMediaIdSubmodule.decode(VALID_API_RESPONSE)).to.deep.equal(VALID_API_RESPONSE); - expect(verizonMediaIdSubmodule.decode(VALID_API_RESPONSE)).to.not.equal(VALID_API_RESPONSE); + const VALID_API_RESPONSES = [{ + key: 'vmiud', + expected: '1234', + payload: { + vmuid: '1234' + } + }, + { + key: 'connectid', + expected: '4567', + payload: { + connectid: '4567' + } + }, + { + key: 'both', + expected: '4567', + payload: { + vmuid: '1234', + connectid: '4567' + } + }]; + VALID_API_RESPONSES.forEach(responseData => { + it('should return a newly constructed object with the connectid for a payload with ${responseData.key} key(s)', () => { + expect(verizonMediaIdSubmodule.decode(responseData.payload)).to.deep.equal( + {connectid: responseData.expected} + ); + }); }); [{}, '', {foo: 'bar'}].forEach((response) => { From 4c047f91e1c3bbc0b495427630f031753c561a42 Mon Sep 17 00:00:00 2001 From: John Salis Date: Tue, 8 Dec 2020 23:48:34 -0500 Subject: [PATCH 086/103] Beachfront adapter: Add banner tagid param (#6086) * add tagid to banner request * bump version * update test case * run tests Co-authored-by: John Salis --- modules/beachfrontBidAdapter.js | 3 ++- test/spec/modules/beachfrontBidAdapter_spec.js | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/beachfrontBidAdapter.js b/modules/beachfrontBidAdapter.js index 4b30f47e2cf..44755a78864 100644 --- a/modules/beachfrontBidAdapter.js +++ b/modules/beachfrontBidAdapter.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.14'; +const ADAPTER_VERSION = '1.15'; const ADAPTER_NAME = 'BFIO_PREBID'; const OUTSTREAM = 'outstream'; @@ -387,6 +387,7 @@ function createBannerRequestData(bids, bidderRequest) { slot: bid.adUnitCode, id: getBannerBidParam(bid, 'appId'), bidfloor: getBannerBidParam(bid, 'bidfloor'), + tagid: getBannerBidParam(bid, 'tagid'), sizes: getBannerSizes(bid) }; }); diff --git a/test/spec/modules/beachfrontBidAdapter_spec.js b/test/spec/modules/beachfrontBidAdapter_spec.js index 661780ffac0..43c71dd6349 100644 --- a/test/spec/modules/beachfrontBidAdapter_spec.js +++ b/test/spec/modules/beachfrontBidAdapter_spec.js @@ -336,6 +336,7 @@ describe('BeachfrontAdapter', function () { const width = 300; const height = 250; const bidRequest = bidRequests[0]; + bidRequest.params.tagid = '7cd7a7b4-ef3f-4aeb-9565-3627f255fa10'; bidRequest.mediaTypes = { banner: { sizes: [ width, height ] @@ -354,6 +355,7 @@ describe('BeachfrontAdapter', function () { slot: bidRequest.adUnitCode, id: bidRequest.params.appId, bidfloor: bidRequest.params.bidfloor, + tagid: bidRequest.params.tagid, sizes: [{ w: width, h: height }] } ]); From cbd1169204dbf7f0bf0a3ea8fea4a85241f8dc11 Mon Sep 17 00:00:00 2001 From: jxdeveloper1 <71084096+jxdeveloper1@users.noreply.github.com> Date: Wed, 9 Dec 2020 12:50:47 +0800 Subject: [PATCH 087/103] changed events endpoint (#6088) --- modules/jixieBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/jixieBidAdapter.js b/modules/jixieBidAdapter.js index d3ae090964c..7c6e0027482 100644 --- a/modules/jixieBidAdapter.js +++ b/modules/jixieBidAdapter.js @@ -9,7 +9,7 @@ import { Renderer } from '../src/Renderer.js'; export const storage = getStorageManager(); const BIDDER_CODE = 'jixie'; -const EVENTS_URL = 'https://jxhbtrackers.azurewebsites.net/sync/evt?'; +const EVENTS_URL = 'https://hbtra.jixie.io/sync/hb?'; const JX_OUTSTREAM_RENDERER_URL = 'https://scripts.jixie.io/jxhboutstream.js'; const REQUESTS_URL = 'https://hb.jixie.io/v2/hbpost'; const sidTTLMins_ = 30; From 478e0455b952e7f67e3b6124a862c8a871ee618b Mon Sep 17 00:00:00 2001 From: Brandon Ling <51931757+blingster7@users.noreply.github.com> Date: Wed, 9 Dec 2020 02:29:33 -0500 Subject: [PATCH 088/103] [Triplelift] Fix FPD key-value pairs logic (#6065) * follow spec to parse fpd * ad unit support stub * adunit method * ad unit etc * typo * fix test * typo * change to const --- modules/tripleliftBidAdapter.js | 31 +++++++++++++++++-- .../spec/modules/tripleliftBidAdapter_spec.js | 31 ++++++++++++++----- 2 files changed, 52 insertions(+), 10 deletions(-) diff --git a/modules/tripleliftBidAdapter.js b/modules/tripleliftBidAdapter.js index d54d76efb41..4679c1faf62 100644 --- a/modules/tripleliftBidAdapter.js +++ b/modules/tripleliftBidAdapter.js @@ -122,6 +122,9 @@ function _buildPostBody(bidRequests) { } else if (bidRequest.mediaTypes.banner) { imp.banner = { format: _sizes(bidRequest.sizes) }; }; + if (!utils.isEmpty(bidRequest.fpd)) { + imp.fpd = _getAdUnitFpd(bidRequest.fpd); + } return imp; }); @@ -183,12 +186,34 @@ function _getFloor (bid) { } function _getGlobalFpd() { - let fpd = {}; + const fpd = {}; + const context = {} + const user = {}; + const fpdContext = Object.assign({}, config.getConfig('fpd.context')); const fpdUser = Object.assign({}, config.getConfig('fpd.user')); - _addEntries(fpd, fpdContext); - _addEntries(fpd, fpdUser); + _addEntries(context, fpdContext); + _addEntries(user, fpdUser); + + if (!utils.isEmpty(context)) { + fpd.context = context; + } + if (!utils.isEmpty(user)) { + fpd.user = user; + } + return fpd; +} + +function _getAdUnitFpd(adUnitFpd) { + const fpd = {}; + const context = {}; + + _addEntries(context, adUnitFpd.context); + + if (!utils.isEmpty(context)) { + fpd.context = context; + } return fpd; } diff --git a/test/spec/modules/tripleliftBidAdapter_spec.js b/test/spec/modules/tripleliftBidAdapter_spec.js index b417876f276..82578424027 100644 --- a/test/spec/modules/tripleliftBidAdapter_spec.js +++ b/test/spec/modules/tripleliftBidAdapter_spec.js @@ -143,6 +143,14 @@ describe('triplelift adapter', function () { auctionId: '1d1a030790a475', userId: {}, schain, + fpd: { + context: { + pbAdSlot: 'homepage-top-rect', + data: { + adUnitSpecificAttribute: 123 + } + } + } }, { bidder: 'triplelift', @@ -597,17 +605,19 @@ describe('triplelift adapter', function () { const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); expect(request.data.imp[0].floor).to.equal(1.99); }); - it('should send fpd on root level ext if kvps are available', function() { + it('should send global config fpd if kvps are available', function() { const sens = null; const category = ['news', 'weather', 'hurricane']; const pmp_elig = 'true'; const fpd = { context: { - pmp_elig, - category, + pmp_elig: pmp_elig, + data: { + category: category + } }, user: { - sens, + sens: sens, } } sandbox.stub(config, 'getConfig').callsFake(key => { @@ -618,9 +628,16 @@ describe('triplelift adapter', function () { }); const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); const { data: payload } = request; - expect(payload.ext.fpd).to.not.haveOwnProperty('sens'); - expect(payload.ext.fpd).to.haveOwnProperty('category'); - expect(payload.ext.fpd).to.haveOwnProperty('pmp_elig'); + expect(payload.ext.fpd.user).to.not.exist; + expect(payload.ext.fpd.context.data).to.haveOwnProperty('category'); + expect(payload.ext.fpd.context).to.haveOwnProperty('pmp_elig'); + }); + it('should send ad unit fpd if kvps are available', function() { + const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); + expect(request.data.imp[0].fpd.context).to.haveOwnProperty('pbAdSlot'); + expect(request.data.imp[0].fpd.context).to.haveOwnProperty('data'); + expect(request.data.imp[0].fpd.context.data).to.haveOwnProperty('adUnitSpecificAttribute'); + expect(request.data.imp[1].fpd).to.not.exist; }); }); From dd51f245abe600801703c70ca688fb55181a8d78 Mon Sep 17 00:00:00 2001 From: nyakove <43004249+nyakove@users.noreply.github.com> Date: Wed, 9 Dec 2020 15:40:23 +0200 Subject: [PATCH 089/103] Add new adapter - adWMGBidAdapter (#6070) * Add adWMG Bid Adapter * Fix unit tests Co-authored-by: Mikhail Dykun --- modules/adWMGBidAdapter.js | 297 ++++++++++++++++++++++ modules/adWMGBidAdapter.md | 34 +++ test/spec/modules/adWMGBidAdapter_spec.js | 292 +++++++++++++++++++++ 3 files changed, 623 insertions(+) create mode 100644 modules/adWMGBidAdapter.js create mode 100644 modules/adWMGBidAdapter.md create mode 100644 test/spec/modules/adWMGBidAdapter_spec.js diff --git a/modules/adWMGBidAdapter.js b/modules/adWMGBidAdapter.js new file mode 100644 index 00000000000..3a0a8a22274 --- /dev/null +++ b/modules/adWMGBidAdapter.js @@ -0,0 +1,297 @@ +'use strict'; + +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { config } from '../src/config.js'; +import { BANNER } from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'adWMG'; +const ENDPOINT = 'https://rtb.adwmg.com/prebid'; +let SYNC_ENDPOINT = 'https://rtb.adwmg.com/cphb.html?'; + +export const spec = { + code: BIDDER_CODE, + aliases: ['wmg'], + supportedMediaTypes: [BANNER], + isBidRequestValid: (bid) => { + if (bid.bidder !== BIDDER_CODE) { + return false; + } + + if (!(bid.params.publisherId)) { + return false; + } + + return true; + }, + buildRequests: (validBidRequests, bidderRequest) => { + const timeout = bidderRequest.timeout || 0; + const debug = config.getConfig('debug') || false; + const referrer = bidderRequest.refererInfo.referer; + const locale = window.navigator.language && window.navigator.language.length > 0 ? window.navigator.language.substr(0, 2) : ''; + const domain = config.getConfig('publisherDomain') || (window.location && window.location.host ? window.location.host : ''); + const ua = window.navigator.userAgent.toLowerCase(); + const additional = spec.parseUserAgent(ua); + + return validBidRequests.map(bidRequest => { + const adUnit = { + code: bidRequest.adUnitCode, + bids: { + bidder: bidRequest.bidder, + params: bidRequest.params + }, + mediaTypes: bidRequest.mediaTypes + }; + + if (bidRequest.hasOwnProperty('sizes') && bidRequest.sizes.length > 0) { + adUnit.sizes = bidRequest.sizes; + } + + const request = { + auctionId: bidRequest.auctionId, + requestId: bidRequest.bidId, + bidRequestsCount: bidRequest.bidRequestsCount, + bidderRequestId: bidRequest.bidderRequestId, + transactionId: bidRequest.transactionId, + referrer: referrer, + timeout: timeout, + adUnit: adUnit, + locale: locale, + domain: domain, + os: additional.os, + osv: additional.osv, + devicetype: additional.devicetype + }; + + if (bidderRequest.gdprConsent) { + request.gdpr = { + applies: bidderRequest.gdprConsent.gdprApplies, + consentString: bidderRequest.gdprConsent.consentString + }; + } + + /* if (bidderRequest.uspConsent) { + request.uspConsent = bidderRequest.uspConsent; + } + */ + if (bidRequest.userId && bidRequest.userId.pubcid) { + request.userId = { + pubcid: bidRequest.userId.pubcid + }; + } + + if (debug) { + request.debug = debug; + } + + return { + method: 'POST', + url: ENDPOINT, + data: JSON.stringify(request) + } + }); + }, + interpretResponse: (serverResponse) => { + const bidResponses = []; + + if (serverResponse.body) { + const response = serverResponse.body; + const bidResponse = { + requestId: response.requestId, + cpm: response.cpm, + width: response.width, + height: response.height, + creativeId: response.creativeId, + currency: response.currency, + netRevenue: response.netRevenue, + ttl: response.ttl, + ad: response.ad, + }; + bidResponses.push(bidResponse); + } + + return bidResponses; + }, + getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { + if (gdprConsent) { + SYNC_ENDPOINT = utils.tryAppendQueryString(SYNC_ENDPOINT, 'gdpr', (gdprConsent.gdprApplies ? 1 : 0)); + } + + if (gdprConsent && typeof gdprConsent.consentString === 'string') { + SYNC_ENDPOINT = utils.tryAppendQueryString(SYNC_ENDPOINT, 'gdpr_consent', gdprConsent.consentString); + } + + /* if (uspConsent) { + SYNC_ENDPOINT = utils.tryAppendQueryString(SYNC_ENDPOINT, 'us_privacy', uspConsent); + } */ + let syncs = []; + if (syncOptions.iframeEnabled) { + syncs.push({ + type: 'iframe', + url: SYNC_ENDPOINT + }); + } + return syncs; + }, + parseUserAgent: (ua) => { + function detectDevice() { + if (/ipad|android 3.0|xoom|sch-i800|playbook|tablet|kindle/i + .test(ua.toLowerCase())) { + return 5; + } + if (/iphone|ipod|android|blackberry|opera|mini|windows\sce|palm|smartphone|iemobile/i + .test(ua.toLowerCase())) { + return 4; + } + if (/smart[-_\s]?tv|hbbtv|appletv|googletv|hdmi|netcast|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b/i + .test(ua.toLowerCase())) { + return 3; + } + return 2; + } + + function detectOs() { + const module = { + options: [], + header: [navigator.platform, ua, navigator.appVersion, navigator.vendor, window.opera], + dataos: [{ + name: 'Windows Phone', + value: 'Windows Phone', + version: 'OS' + }, + { + name: 'Windows', + value: 'Win', + version: 'NT' + }, + { + name: 'iOS', + value: 'iPhone', + version: 'OS' + }, + { + name: 'iOS', + value: 'iPad', + version: 'OS' + }, + { + name: 'Kindle', + value: 'Silk', + version: 'Silk' + }, + { + name: 'Android', + value: 'Android', + version: 'Android' + }, + { + name: 'PlayBook', + value: 'PlayBook', + version: 'OS' + }, + { + name: 'BlackBerry', + value: 'BlackBerry', + version: '/' + }, + { + name: 'Macintosh', + value: 'Mac', + version: 'OS X' + }, + { + name: 'Linux', + value: 'Linux', + version: 'rv' + }, + { + name: 'Palm', + value: 'Palm', + version: 'PalmOS' + } + ], + init: function () { + var agent = this.header.join(' '); + var os = this.matchItem(agent, this.dataos); + return { + os + }; + }, + + getVersion: function (name, version) { + if (name === 'Windows') { + switch (parseFloat(version).toFixed(1)) { + case '5.0': + return '2000'; + case '5.1': + return 'XP'; + case '5.2': + return 'Server 2003'; + case '6.0': + return 'Vista'; + case '6.1': + return '7'; + case '6.2': + return '8'; + case '6.3': + return '8.1'; + default: + return version || 'other'; + } + } else return version || 'other'; + }, + + matchItem: function (string, data) { + var i = 0; + var j = 0; + var regex, regexv, match, matches, version; + + for (i = 0; i < data.length; i += 1) { + regex = new RegExp(data[i].value, 'i'); + match = regex.test(string); + if (match) { + regexv = new RegExp(data[i].version + '[- /:;]([\\d._]+)', 'i'); + matches = string.match(regexv); + version = ''; + if (matches) { + if (matches[1]) { + matches = matches[1]; + } + } + if (matches) { + matches = matches.split(/[._]+/); + for (j = 0; j < matches.length; j += 1) { + if (j === 0) { + version += matches[j] + '.'; + } else { + version += matches[j]; + } + } + } else { + version = 'other'; + } + return { + name: data[i].name, + version: this.getVersion(data[i].name, version) + }; + } + } + return { + name: 'unknown', + version: 'other' + }; + } + }; + + var e = module.init(); + + return { + os: e.os.name || '', + osv: e.os.version || '' + } + } + + return {devicetype: detectDevice(), os: detectOs().os, osv: detectOs().osv} + } +} +registerBidder(spec); diff --git a/modules/adWMGBidAdapter.md b/modules/adWMGBidAdapter.md new file mode 100644 index 00000000000..8c277b803db --- /dev/null +++ b/modules/adWMGBidAdapter.md @@ -0,0 +1,34 @@ +# Overview + +``` +Module Name: adWMG Adapter +Module Type: Bidder Adapter +Maintainer: wbid@adwmg.com +``` + +# Description + +Module that connects to adWMG demand sources to fetch bids. Supports 'banner' ad format. + +# Bid Parameters + +| Key | Required | Example | Description | +| --- | -------- | ------- | ----------- | +| `publisherId` | yes | `'5cebea3c9eea646c7b623d5e'` | publisher ID from WMG Dashboard | +| `IABCategories` | no | `['IAB1', 'IAB5']` | IAB ad categories for adUnit | + + +# Test Parameters + +```javascript +var adUnits = [{ + code: 'wmg-test-div', + sizes: [[300, 250]], + bids: [{ + bidder: 'adWMG', + params: { + publisherId: '5cebea3c9eea646c7b623d5e', + IABCategories: ['IAB1', 'IAB5'] + }, + }] +}] \ No newline at end of file diff --git a/test/spec/modules/adWMGBidAdapter_spec.js b/test/spec/modules/adWMGBidAdapter_spec.js new file mode 100644 index 00000000000..5c2364d454c --- /dev/null +++ b/test/spec/modules/adWMGBidAdapter_spec.js @@ -0,0 +1,292 @@ +import { expect } from 'chai'; +import { spec } from 'modules/adWMGBidAdapter.js'; +import { config } from 'src/config.js'; + +describe('adWMGBidAdapter', function () { + describe('isBidRequestValid', function () { + let bid; + beforeEach(function() { + bid = { + bidder: 'adWMG', + params: { + publisherId: '5cebea3c9eea646c7b623d5e' + }, + mediaTypes: { + banner: { + size: [[300, 250]] + } + } + }; + }); + + it('should return true when valid bid request is set', function() { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when bidder is not set to "adWMG"', function() { + bid.bidder = 'bidder'; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when \'publisherId\' param are not set', function() { + delete bid.params.publisherId; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('parseUserAgent', function() { + let ua_desktop, ua_mobile, ua_tv, ua_tablet; + beforeEach(function() { + ua_desktop = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36'; + ua_tv = 'Mozilla/5.0 (Linux; NetCast; U) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/38.0.2125.122 Safari/537.31 SmartTV/7.0'; + ua_mobile = 'Mozilla/5.0 (Linux; Android 7.0; SAMSUNG SM-G610M Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/7.2 Chrome/59.0.3071.125 Mobile Safari/537.36'; + ua_tablet = 'Mozilla/5.0 (iPad; CPU OS 7_1_2 like Mac OS X) AppleWebKit/537.51.2 (KHTML, like Gecko) Version/7.0 Mobile/11D257 Safari/9537.53'; + }); + + it('should return correct device type: desktop', function() { + let userDeviceInfo = spec.parseUserAgent(ua_desktop); + expect(userDeviceInfo.devicetype).to.equal(2); + }); + + it('should return correct device type: TV', function() { + let userDeviceInfo = spec.parseUserAgent(ua_tv); + expect(userDeviceInfo.devicetype).to.equal(3); + }); + + it('should return correct device type: mobile', function() { + let userDeviceInfo = spec.parseUserAgent(ua_mobile); + expect(userDeviceInfo.devicetype).to.equal(4); + }); + + it('should return correct device type: tablet', function() { + let userDeviceInfo = spec.parseUserAgent(ua_tablet); + expect(userDeviceInfo.devicetype).to.equal(5); + }); + + it('should return correct OS name', function() { + let userDeviceInfo = spec.parseUserAgent(ua_desktop); + expect(userDeviceInfo.os).to.equal('Windows'); + }); + + it('should return correct OS version', function() { + let userDeviceInfo = spec.parseUserAgent(ua_desktop); + expect(userDeviceInfo.osv).to.equal('10.0'); + }); + }); + + describe('buildRequests', function () { + let bidRequests; + beforeEach(function() { + bidRequests = [ + { + bidder: 'adWMG', + adUnitCode: 'adwmg-test-ad', + auctionId: 'test-auction-id', + bidId: 'test-bid-id', + bidRequestsCount: 1, + bidderRequestId: 'bidderrequestid123', + transactionId: 'transaction-id-123', + sizes: [[300, 250]], + requestId: 'requestid123', + params: { + floorPrice: 100, + currency: 'USD' + }, + mediaTypes: { + banner: { + size: [[300, 250]] + } + }, + userId: { + pubcid: 'pubc-id-123' + } + }, { + bidder: 'adWMG', + adUnitCode: 'adwmg-test-ad-2', + auctionId: 'test-auction-id-2', + bidId: 'test-bid-id-2', + bidRequestsCount: 1, + bidderRequestId: 'bidderrequestid456', + transactionId: 'transaction-id-456', + sizes: [[320, 50]], + requestId: 'requestid456', + params: { + floorPrice: 100, + currency: 'USD' + }, + mediaTypes: { + banner: { + size: [[320, 50]] + } + }, + userId: { + pubcid: 'pubc-id-456' + } + } + ]; + }); + + let bidderRequest = { + refererInfo: { + referer: 'https://test.com' + }, + gdprConsent: { + consentString: 'CO9rhBTO9rhBTAcABBENBCCsAP_AAH_AACiQHItf_X_fb3_j-_59_9t0eY1f9_7_v20zjgeds-8Nyd_X_L8X42M7vB36pq4KuR4Eu3LBIQdlHOHcTUmw6IkVqTPsbk2Mr7NKJ7PEinMbe2dYGH9_n9XTuZKY79_s___z__-__v__7_f_r-3_3_vp9V---3YHIgEmGpfARZiWOBJNGlUKIEIVxIdACACihGFomsICVwU7K4CP0EDABAagIwIgQYgoxZBAAAAAElEQEgB4IBEARAIAAQAqQEIACNAEFgBIGAQACgGhYARQBCBIQZHBUcpgQESLRQTyVgCUXexhhCGUUANAg4AA.YAAAAAAAAAAA', + vendorData: {}, + gdprApplies: true, + apiVersion: 2 + } + }; + + it('should not contain a sizes when sizes is not set', function() { + delete bidRequests[0].sizes; + delete bidRequests[1].sizes; + let requests = spec.buildRequests(bidRequests, bidderRequest); + expect(JSON.parse(requests[0].data).sizes).to.be.an('undefined'); + expect(JSON.parse(requests[1].data).sizes).to.be.an('undefined'); + }); + + it('should not contain a userId when userId is not set', function() { + delete bidRequests[0].userId; + delete bidRequests[1].userId; + let requests = spec.buildRequests(bidRequests, bidderRequest); + expect(JSON.parse(requests[0].data).userId).to.be.an('undefined'); + expect(JSON.parse(requests[1].data).userId).to.be.an('undefined'); + }); + + it('should have a post method', function() { + let requests = spec.buildRequests(bidRequests, bidderRequest); + expect(requests[0].method).to.equal('POST'); + expect(requests[1].method).to.equal('POST'); + }); + + it('should contain a request id equals to the bid id', function() { + let requests = spec.buildRequests(bidRequests, bidderRequest); + expect(JSON.parse(requests[0].data).requestId).to.equal(bidRequests[0].bidId); + expect(JSON.parse(requests[1].data).requestId).to.equal(bidRequests[1].bidId); + }); + + it('should have an url that match the default endpoint', function() { + let requests = spec.buildRequests(bidRequests, bidderRequest); + expect(requests[0].url).to.equal('https://rtb.adwmg.com/prebid'); + expect(requests[1].url).to.equal('https://rtb.adwmg.com/prebid'); + }); + + it('should contain GDPR consent data if GDPR set', function() { + let requests = spec.buildRequests(bidRequests, bidderRequest); + expect(JSON.parse(requests[0].data).gdpr.applies).to.be.true; + expect(JSON.parse(requests[0].data).gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); + expect(JSON.parse(requests[1].data).gdpr.applies).to.be.true; + expect(JSON.parse(requests[1].data).gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); + }) + + it('should not contain GDPR consent data if GDPR not set', function() { + delete bidderRequest.gdprConsent; + let requests = spec.buildRequests(bidRequests, bidderRequest); + expect(JSON.parse(requests[0].data).gdpr).to.be.an('undefined'); + expect(JSON.parse(requests[1].data).gdpr).to.be.an('undefined'); + }) + + it('should set debug mode in requests if enabled', function() { + sinon.stub(config, 'getConfig').withArgs('debug').returns(true); + let requests = spec.buildRequests(bidRequests, bidderRequest); + expect(JSON.parse(requests[0].data).debug).to.be.true; + expect(JSON.parse(requests[1].data).debug).to.be.true; + config.getConfig.restore(); + }) + }); + + describe('interpretResponse', function () { + let serverResponse; + beforeEach(function() { + serverResponse = { + body: { + 'requestId': 'request-id', + 'cpm': 100, + 'width': 300, + 'height': 250, + 'ad': '
ad
', + 'ttl': 300, + 'creativeId': 'creative-id', + 'netRevenue': true, + 'currency': 'USD' + } + }; + }); + + it('should return a valid response', () => { + var responses = spec.interpretResponse(serverResponse); + expect(responses).to.be.an('array').that.is.not.empty; + + let response = responses[0]; + expect(response).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + 'netRevenue', 'currency'); + expect(response.requestId).to.equal('request-id'); + expect(response.cpm).to.equal(100); + expect(response.width).to.equal(300); + expect(response.height).to.equal(250); + expect(response.ad).to.equal('
ad
'); + expect(response.ttl).to.equal(300); + expect(response.creativeId).to.equal('creative-id'); + expect(response.netRevenue).to.be.true; + expect(response.currency).to.equal('USD'); + }); + + it('should return an empty array when serverResponse is empty', () => { + serverResponse = {}; + var responses = spec.interpretResponse(serverResponse); + expect(responses).to.deep.equal([]); + }); + }); + + describe('getUserSyncs', function () { + it('should return nothing when sync is disabled', function () { + const syncOptions = { + 'iframeEnabled': false, + 'pixelEnabled': false + }; + + let syncs = spec.getUserSyncs(syncOptions); + expect(syncs).to.deep.equal([]); + }); + + it('should register iframe sync when only iframe is enabled', function () { + const syncOptions = { + 'iframeEnabled': true, + 'pixelEnabled': false + }; + + let syncs = spec.getUserSyncs(syncOptions); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).includes('https://rtb.adwmg.com/cphb.html?'); + }); + + it('should register iframe sync when iframe and image are enabled', function () { + const syncOptions = { + 'iframeEnabled': true, + 'pixelEnabled': true + }; + + let syncs = spec.getUserSyncs(syncOptions); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).includes('https://rtb.adwmg.com/cphb.html?'); + }); + + it('should send GDPR consent if enabled', function() { + const syncOptions = { + 'iframeEnabled': true, + 'pixelEnabled': true + }; + const gdprConsent = { + consentString: 'CO9rhBTO9rhBTAcABBENBCCsAP_AAH_AACiQHItf_X_fb3_j-_59_9t0eY1f9_7_v20zjgeds-8Nyd_X_L8X42M7vB36pq4KuR4Eu3LBIQdlHOHcTUmw6IkVqTPsbk2Mr7NKJ7PEinMbe2dYGH9_n9XTuZKY79_s___z__-__v__7_f_r-3_3_vp9V---3YHIgEmGpfARZiWOBJNGlUKIEIVxIdACACihGFomsICVwU7K4CP0EDABAagIwIgQYgoxZBAAAAAElEQEgB4IBEARAIAAQAqQEIACNAEFgBIGAQACgGhYARQBCBIQZHBUcpgQESLRQTyVgCUXexhhCGUUANAg4AA.YAAAAAAAAAAA', + vendorData: {}, + gdprApplies: true, + apiVersion: 2 + }; + const serverResponse = {}; + let syncs = spec.getUserSyncs(syncOptions, serverResponse, gdprConsent); + expect(syncs[0].url).includes('gdpr=1'); + expect(syncs[0].url).includes(`gdpr_consent=${gdprConsent.consentString}`); + }); + }); +}); From 9143962e09d3fcb00f7b9b23cff7d74be4502332 Mon Sep 17 00:00:00 2001 From: Stephen Johnston Date: Wed, 9 Dec 2020 09:45:14 -0500 Subject: [PATCH 090/103] Add PubWise Bid Adapter (#6044) * updates for first cut at bidder * fix up height and width * adds test spec * remove hello_world from commit' * updates to support native and fix issues found in initial review * fix handling of new node in response for native vs banner * updates to handle OpenRTB base * updates to support RTB style calls * updates to get back to parity * updates to testing * updates to test media type handling * updates to handling testing * updates to testing * remove report file * updates to fix up unit/spec tests * updates to fix up unit/spec tests * updates to fix up unit/spec tests * updates to handling of gdpr * Delete hello_world.html * remove hellow-world-sample * Pubwise 481 (#7) * updates to support PubWise bid adapter, test cases and documentation * updates to fix param tes * Pubwise 481 (#8) * fixes for unit testing * remove unused variables and params * Updates to Remove Unused Vars (#9) * remove unused vars * updates to fix up used and unsused params * updates to fix up used and unsused params (#10) * updates to fix up used and unsused params * updates to remove usersync and add gvlid * Pubwise 481 (#11) * updates to remove usersync, add https, and add gvlid * Update pubwiseBidAdapter.js * updates to remove json, to remove options hit --- modules/pubwiseBidAdapter.js | 777 ++++++++++++++++++++ modules/pubwiseBidAdapter.md | 78 ++ test/spec/modules/pubwiseBidAdapter_spec.js | 575 +++++++++++++++ 3 files changed, 1430 insertions(+) create mode 100644 modules/pubwiseBidAdapter.js create mode 100644 modules/pubwiseBidAdapter.md create mode 100644 test/spec/modules/pubwiseBidAdapter_spec.js diff --git a/modules/pubwiseBidAdapter.js b/modules/pubwiseBidAdapter.js new file mode 100644 index 00000000000..f450a8bede8 --- /dev/null +++ b/modules/pubwiseBidAdapter.js @@ -0,0 +1,777 @@ +import * as utils from '../src/utils.js'; +import { config } from '../src/config.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE } from '../src/mediaTypes.js'; +const VERSION = '0.1.0'; +const GVLID = 842; +const NET_REVENUE = true; +const UNDEFINED = undefined; +const DEFAULT_CURRENCY = 'USD'; +const AUCTION_TYPE = 1; +const BIDDER_CODE = 'pwbid'; +const ENDPOINT_URL = 'https://bid.pubwise.io/prebid'; +const DEFAULT_WIDTH = 0; +const DEFAULT_HEIGHT = 0; +const PREBID_NATIVE_HELP_LINK = 'https://prebid.org/dev-docs/show-native-ads.html'; +// const USERSYNC_URL = '//127.0.0.1:8080/usersync' + +const CUSTOM_PARAMS = { + 'gender': '', // User gender + 'yob': '', // User year of birth + 'lat': '', // User location - Latitude + 'lon': '', // User Location - Longitude +}; + +// rtb native types are meant to be dynamic and extendable +// the extendable data asset types are nicely aligned +// in practice we set an ID that is distinct for each real type of return +const NATIVE_ASSETS = { + 'TITLE': { ID: 1, KEY: 'title', TYPE: 0 }, + 'IMAGE': { ID: 2, KEY: 'image', TYPE: 0 }, + 'ICON': { ID: 3, KEY: 'icon', TYPE: 0 }, + 'SPONSOREDBY': { ID: 4, KEY: 'sponsoredBy', TYPE: 1 }, + 'BODY': { ID: 5, KEY: 'body', TYPE: 2 }, + 'CLICKURL': { ID: 6, KEY: 'clickUrl', TYPE: 0 }, + 'VIDEO': { ID: 7, KEY: 'video', TYPE: 0 }, + 'EXT': { ID: 8, KEY: 'ext', TYPE: 0 }, + 'DATA': { ID: 9, KEY: 'data', TYPE: 0 }, + 'LOGO': { ID: 10, KEY: 'logo', TYPE: 0 }, + 'SPONSORED': { ID: 11, KEY: 'sponsored', TYPE: 1 }, + 'DESC': { ID: 12, KEY: 'data', TYPE: 2 }, + 'RATING': { ID: 13, KEY: 'rating', TYPE: 3 }, + 'LIKES': { ID: 14, KEY: 'likes', TYPE: 4 }, + 'DOWNLOADS': { ID: 15, KEY: 'downloads', TYPE: 5 }, + 'PRICE': { ID: 16, KEY: 'price', TYPE: 6 }, + 'SALEPRICE': { ID: 17, KEY: 'saleprice', TYPE: 7 }, + 'PHONE': { ID: 18, KEY: 'phone', TYPE: 8 }, + 'ADDRESS': { ID: 19, KEY: 'address', TYPE: 9 }, + 'DESC2': { ID: 20, KEY: 'desc2', TYPE: 10 }, + 'DISPLAYURL': { ID: 21, KEY: 'displayurl', TYPE: 11 }, + 'CTA': { ID: 22, KEY: 'cta', TYPE: 12 } +}; + +const NATIVE_ASSET_IMAGE_TYPE = { + 'ICON': 1, + 'LOGO': 2, + 'IMAGE': 3 +} + +// to render any native unit we have to have a few items +const NATIVE_MINIMUM_REQUIRED_IMAGE_ASSETS = [ + { + id: NATIVE_ASSETS.SPONSOREDBY.ID, + required: true, + data: { + type: 1 + } + }, + { + id: NATIVE_ASSETS.TITLE.ID, + required: true, + }, + { + id: NATIVE_ASSETS.IMAGE.ID, + required: true, + } +] + +let isInvalidNativeRequest = false +let NATIVE_ASSET_ID_TO_KEY_MAP = {}; +let NATIVE_ASSET_KEY_TO_ASSET_MAP = {}; + +// together allows traversal of NATIVE_ASSETS_LIST in any direction +// id -> key +utils._each(NATIVE_ASSETS, anAsset => { NATIVE_ASSET_ID_TO_KEY_MAP[anAsset.ID] = anAsset.KEY }); +// key -> asset +utils._each(NATIVE_ASSETS, anAsset => { NATIVE_ASSET_KEY_TO_ASSET_MAP[anAsset.KEY] = anAsset }); + +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + supportedMediaTypes: [BANNER, NATIVE], + /** + * 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) { + // siteId is required + if (bid.params && bid.params.siteId) { + // it must be a string + if (!utils.isStr(bid.params.siteId)) { + _logWarn('siteId is required for bid', bid); + return false; + } + } else { + return false; + } + + return true; + }, + /** + * 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) { + var refererInfo; + if (bidderRequest && bidderRequest.refererInfo) { + refererInfo = bidderRequest.refererInfo; + } + var conf = _initConf(refererInfo); + var payload = _createOrtbTemplate(conf); + var bidCurrency = ''; + var bid; + var blockedIabCategories = []; + + validBidRequests.forEach(originalBid => { + bid = utils.deepClone(originalBid); + bid.params.adSlot = bid.params.adSlot || ''; + _parseAdSlot(bid); + + conf = _handleCustomParams(bid.params, conf); + conf.transactionId = bid.transactionId; + bidCurrency = bid.params.currency || UNDEFINED; + bid.params.currency = bidCurrency; + + if (bid.params.hasOwnProperty('bcat') && utils.isArray(bid.params.bcat)) { + blockedIabCategories = blockedIabCategories.concat(bid.params.bcat); + } + + var impObj = _createImpressionObject(bid, conf); + if (impObj) { + payload.imp.push(impObj); + } + }); + + // no payload imps, no rason to continue + if (payload.imp.length == 0) { + return; + } + + // test bids can also be turned on here + if (window.location.href.indexOf('pubwiseTestBid=true') !== -1) { + payload.test = 1; + } + + if (bid.params.isTest) { + payload.test = Number(bid.params.isTest) // should be 1 or 0 + } + payload.site.publisher.id = bid.params.siteId.trim(); + payload.user.gender = (conf.gender ? conf.gender.trim() : UNDEFINED); + payload.user.geo = {}; + payload.user.geo.lat = _parseSlotParam('lat', conf.lat); + payload.user.geo.lon = _parseSlotParam('lon', conf.lon); + payload.user.yob = _parseSlotParam('yob', conf.yob); + payload.device.geo = payload.user.geo; + payload.site.page = payload.site.page.trim(); + payload.site.domain = _getDomainFromURL(payload.site.page); + + // add the content object from config in request + if (typeof config.getConfig('content') === 'object') { + payload.site.content = config.getConfig('content'); + } + + // merge the device from config.getConfig('device') + if (typeof config.getConfig('device') === 'object') { + payload.device = Object.assign(payload.device, config.getConfig('device')); + } + + // passing transactionId in source.tid + utils.deepSetValue(payload, 'source.tid', conf.transactionId); + + // schain + if (validBidRequests[0].schain) { + utils.deepSetValue(payload, 'source.ext.schain', validBidRequests[0].schain); + } + + // gdpr consent + if (bidderRequest && bidderRequest.gdprConsent) { + utils.deepSetValue(payload, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + utils.deepSetValue(payload, 'regs.ext.gdpr', (bidderRequest.gdprConsent.gdprApplies ? 1 : 0)); + } + + // ccpa on the root object + if (bidderRequest && bidderRequest.uspConsent) { + utils.deepSetValue(payload, 'regs.ext.us_privacy', bidderRequest.uspConsent); + } + + // if coppa is in effect then note it + if (config.getConfig('coppa') === true) { + utils.deepSetValue(payload, 'regs.coppa', 1); + } + + var options = {contentType: 'text/plain'} + + _logInfo('buildRequests payload', payload); + _logInfo('buildRequests bidderRequest', bidderRequest); + + return { + method: 'POST', + url: ENDPOINT_URL, + data: payload, + options: options, + bidderRequest: bidderRequest, + }; + }, + /** + * 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 (response, request) { + const bidResponses = []; + var respCur = DEFAULT_CURRENCY; + _logInfo('interpretResponse request', request); + let parsedRequest = request.data; // not currently stringified + // let parsedReferrer = parsedRequest.site && parsedRequest.site.ref ? parsedRequest.site.ref : ''; + + // try { + if (response.body && response.body.seatbid && utils.isArray(response.body.seatbid)) { + // Supporting multiple bid responses for same adSize + respCur = response.body.cur || respCur; + response.body.seatbid.forEach(seatbidder => { + seatbidder.bid && + utils.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, + currency: respCur, + netRevenue: NET_REVENUE, + ttl: 300, + ad: bid.adm, + pw_seat: seatbidder.seat || null, + pw_dspid: bid.ext && bid.ext.dspid ? bid.ext.dspid : null, + partnerImpId: bid.id || '' // partner impression Id + }; + if (parsedRequest.imp && parsedRequest.imp.length > 0) { + parsedRequest.imp.forEach(req => { + if (bid.impid === req.id) { + _checkMediaType(bid.adm, newBid); + switch (newBid.mediaType) { + case BANNER: + break; + case NATIVE: + _parseNativeResponse(bid, newBid); + break; + } + } + }); + } + + newBid.meta = {}; + if (bid.ext && bid.ext.dspid) { + newBid.meta.networkId = bid.ext.dspid; + } + if (bid.ext && bid.ext.advid) { + newBid.meta.buyerId = bid.ext.advid; + } + if (bid.adomain && bid.adomain.length > 0) { + newBid.meta.advertiserDomains = bid.adomain; + newBid.meta.clickUrl = bid.adomain[0]; + } + + bidResponses.push(newBid); + }); + }); + } + // } catch (error) { + // _logError(error); + // } + return bidResponses; + } +} + +function _checkMediaType(adm, newBid) { + // Create a regex here to check the strings + var admJSON = ''; + if (adm.indexOf('"ver":') >= 0) { + try { + admJSON = JSON.parse(adm.replace(/\\/g, '')); + if (admJSON && admJSON.assets) { + newBid.mediaType = NATIVE; + } + } catch (e) { + _logWarn('Error: Cannot parse native reponse for ad response: ' + adm); + } + } else { + newBid.mediaType = BANNER; + } +} + +function _parseNativeResponse(bid, newBid) { + newBid.native = {}; + if (bid.hasOwnProperty('adm')) { + var adm = ''; + try { + adm = JSON.parse(bid.adm.replace(/\\/g, '')); + } catch (ex) { + _logWarn('Error: Cannot parse native reponse for ad response: ' + newBid.adm); + return; + } + if (adm && adm.assets && adm.assets.length > 0) { + newBid.mediaType = NATIVE; + for (let i = 0, len = adm.assets.length; i < len; i++) { + switch (adm.assets[i].id) { + case NATIVE_ASSETS.TITLE.ID: + newBid.native.title = adm.assets[i].title && adm.assets[i].title.text; + break; + case NATIVE_ASSETS.IMAGE.ID: + newBid.native.image = { + url: adm.assets[i].img && adm.assets[i].img.url, + height: adm.assets[i].img && adm.assets[i].img.h, + width: adm.assets[i].img && adm.assets[i].img.w, + }; + break; + case NATIVE_ASSETS.ICON.ID: + newBid.native.icon = { + url: adm.assets[i].img && adm.assets[i].img.url, + height: adm.assets[i].img && adm.assets[i].img.h, + width: adm.assets[i].img && adm.assets[i].img.w, + }; + break; + case NATIVE_ASSETS.SPONSOREDBY.ID: + case NATIVE_ASSETS.BODY.ID: + case NATIVE_ASSETS.LIKES.ID: + case NATIVE_ASSETS.DOWNLOADS.ID: + case NATIVE_ASSETS.PRICE: + case NATIVE_ASSETS.SALEPRICE.ID: + case NATIVE_ASSETS.PHONE.ID: + case NATIVE_ASSETS.ADDRESS.ID: + case NATIVE_ASSETS.DESC2.ID: + case NATIVE_ASSETS.CTA.ID: + case NATIVE_ASSETS.RATING.ID: + case NATIVE_ASSETS.DISPLAYURL.ID: + newBid.native[NATIVE_ASSET_ID_TO_KEY_MAP[adm.assets[i].id]] = adm.assets[i].data && adm.assets[i].data.value; + break; + } + } + newBid.clickUrl = adm.link && adm.link.url; + newBid.clickTrackers = (adm.link && adm.link.clicktrackers) || []; + newBid.impressionTrackers = adm.imptrackers || []; + newBid.jstracker = adm.jstracker || []; + if (!newBid.width) { + newBid.width = DEFAULT_WIDTH; + } + if (!newBid.height) { + newBid.height = DEFAULT_HEIGHT; + } + } + } +} + +function _getDomainFromURL(url) { + let anchor = document.createElement('a'); + anchor.href = url; + return anchor.hostname; +} + +function _handleCustomParams(params, conf) { + var key, value, entry; + for (key in CUSTOM_PARAMS) { + if (CUSTOM_PARAMS.hasOwnProperty(key)) { + value = params[key]; + if (value) { + entry = CUSTOM_PARAMS[key]; + + if (typeof entry === 'object') { + // will be used in future when we want to + // process a custom param before using + // 'keyname': {f: function() {}} + value = entry.f(value, conf); + } + + if (utils.isStr(value)) { + conf[key] = value; + } else { + _logWarn('Ignoring param : ' + key + ' with value : ' + CUSTOM_PARAMS[key] + ', expects string-value, found ' + typeof value); + } + } + } + } + return conf; +} + +function _createOrtbTemplate(conf) { + return { + id: '' + new Date().getTime(), + at: AUCTION_TYPE, + cur: [DEFAULT_CURRENCY], + imp: [], + site: { + page: conf.pageURL, + ref: conf.refURL, + publisher: {} + }, + device: { + ua: navigator.userAgent, + js: 1, + dnt: (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0, + h: screen.height, + w: screen.width, + language: navigator.language + }, + user: {}, + ext: { + version: VERSION + } + }; +} + +function _createImpressionObject(bid, conf) { + var impObj = {}; + var bannerObj; + var nativeObj = {}; + var mediaTypes = ''; + + impObj = { + id: bid.bidId, + tagid: bid.params.adUnit || undefined, + bidfloor: _parseSlotParam('bidFloor', bid.params.bidFloor), // capitalization dicated by 3.2.4 spec + secure: 1, + bidfloorcur: bid.params.currency ? _parseSlotParam('currency', bid.params.currency) : DEFAULT_CURRENCY // capitalization dicated by 3.2.4 spec + }; + + if (bid.hasOwnProperty('mediaTypes')) { + for (mediaTypes in bid.mediaTypes) { + switch (mediaTypes) { + case BANNER: + bannerObj = _createBannerRequest(bid); + if (bannerObj !== UNDEFINED) { + impObj.banner = bannerObj; + } + break; + case NATIVE: + nativeObj['request'] = JSON.stringify(_createNativeRequest(bid.nativeParams)); + if (!isInvalidNativeRequest) { + impObj.native = nativeObj; + } else { + _logWarn('Error: Error in Native adunit ' + bid.params.adUnit + '. Ignoring the adunit. Refer to ' + PREBID_NATIVE_HELP_LINK + ' for more details.'); + } + break; + } + } + } else { + _logWarn('MediaTypes are Required for all Adunit Configs', bid) + } + + _addFloorFromFloorModule(impObj, bid); + + return impObj.hasOwnProperty(BANNER) || + impObj.hasOwnProperty(NATIVE) ? impObj : UNDEFINED; +} + +function _parseSlotParam(paramName, paramValue) { + if (!utils.isStr(paramValue)) { + paramValue && _logWarn('Ignoring param key: ' + paramName + ', expects string-value, found ' + typeof paramValue); + return UNDEFINED; + } + + switch (paramName) { + case 'bidFloor': + return parseFloat(paramValue) || UNDEFINED; + case 'lat': + return parseFloat(paramValue) || UNDEFINED; + case 'lon': + return parseFloat(paramValue) || UNDEFINED; + case 'yob': + return parseInt(paramValue) || UNDEFINED; + default: + return paramValue; + } +} + +function _parseAdSlot(bid) { + _logInfo('parseAdSlot bid', bid) + bid.params.adUnit = ''; + bid.params.width = 0; + bid.params.height = 0; + bid.params.adSlot = _cleanSlotName(bid.params.adSlot); + + if (bid.hasOwnProperty('mediaTypes')) { + if (bid.mediaTypes.hasOwnProperty(BANNER) && + bid.mediaTypes.banner.hasOwnProperty('sizes')) { // if its a banner, has mediaTypes and sizes + var i = 0; + var sizeArray = []; + for (;i < bid.mediaTypes.banner.sizes.length; i++) { + if (bid.mediaTypes.banner.sizes[i].length === 2) { // sizes[i].length will not be 2 in case where size is set as fluid, we want to skip that entry + sizeArray.push(bid.mediaTypes.banner.sizes[i]); + } + } + bid.mediaTypes.banner.sizes = sizeArray; + if (bid.mediaTypes.banner.sizes.length >= 1) { + // if there is more than one size then pop one onto the banner params width + // pop the first into the params, then remove it from mediaTypes + bid.params.width = bid.mediaTypes.banner.sizes[0][0]; + bid.params.height = bid.mediaTypes.banner.sizes[0][1]; + bid.mediaTypes.banner.sizes = bid.mediaTypes.banner.sizes.splice(1, bid.mediaTypes.banner.sizes.length - 1); + } + } + } else { + _logWarn('MediaTypes are Required for all Adunit Configs', bid) + } +} + +function _cleanSlotName(slotName) { + if (utils.isStr(slotName)) { + return slotName.replace(/^\s+/g, '').replace(/\s+$/g, ''); + } + return ''; +} + +function _initConf(refererInfo) { + return { + pageURL: (refererInfo && refererInfo.referer) ? refererInfo.referer : window.location.href, + refURL: window.document.referrer + }; +} + +function _commonNativeRequestObject(nativeAsset, params) { + var key = nativeAsset.KEY; + return { + id: nativeAsset.ID, + required: params[key].required ? 1 : 0, + data: { + type: nativeAsset.TYPE, + len: params[key].len, + ext: params[key].ext + } + }; +} + +function _addFloorFromFloorModule(impObj, bid) { + let bidFloor = -1; // indicates no floor + + // get lowest floor from floorModule + if (typeof bid.getFloor === 'function' && !config.getConfig('pubwise.disableFloors')) { + [BANNER, NATIVE].forEach(mediaType => { + if (impObj.hasOwnProperty(mediaType)) { + let floorInfo = bid.getFloor({ currency: impObj.bidFloorCur, mediaType: mediaType, size: '*' }); + if (typeof floorInfo === 'object' && floorInfo.currency === impObj.bidFloorCur && !isNaN(parseInt(floorInfo.floor))) { + let mediaTypeFloor = parseFloat(floorInfo.floor); + bidFloor = (bidFloor == -1 ? mediaTypeFloor : Math.min(mediaTypeFloor, bidFloor)) + } + } + }); + } + + // get highest, if none then take the default -1 + if (impObj.bidfloor) { + bidFloor = Math.max(bidFloor, impObj.bidfloor) + } + + // assign if it has a valid floor - > 0 + impObj.bidfloor = ((!isNaN(bidFloor) && bidFloor > 0) ? bidFloor : UNDEFINED); +} + +function _createNativeRequest(params) { + var nativeRequestObject = { + assets: [] + }; + for (var key in params) { + if (params.hasOwnProperty(key)) { + var assetObj = {}; + if (!(nativeRequestObject.assets && nativeRequestObject.assets.length > 0 && nativeRequestObject.assets.hasOwnProperty(key))) { + switch (key) { + case NATIVE_ASSETS.TITLE.KEY: + if (params[key].len || params[key].length) { + assetObj = { + id: NATIVE_ASSETS.TITLE.ID, + required: params[key].required ? 1 : 0, + title: { + len: params[key].len || params[key].length, + ext: params[key].ext + } + }; + } else { + _logWarn('Error: Title Length is required for native ad: ' + JSON.stringify(params)); + } + break; + case NATIVE_ASSETS.IMAGE.KEY: + if (params[key].sizes && params[key].sizes.length > 0) { + assetObj = { + id: NATIVE_ASSETS.IMAGE.ID, + required: params[key].required ? 1 : 0, + img: { + type: NATIVE_ASSET_IMAGE_TYPE.IMAGE, + w: params[key].w || params[key].width || (params[key].sizes ? params[key].sizes[0] : UNDEFINED), + h: params[key].h || params[key].height || (params[key].sizes ? params[key].sizes[1] : UNDEFINED), + wmin: params[key].wmin || params[key].minimumWidth || (params[key].minsizes ? params[key].minsizes[0] : UNDEFINED), + hmin: params[key].hmin || params[key].minimumHeight || (params[key].minsizes ? params[key].minsizes[1] : UNDEFINED), + mimes: params[key].mimes, + ext: params[key].ext, + } + }; + } else { + _logWarn('Error: Image sizes is required for native ad: ' + JSON.stringify(params)); + } + break; + case NATIVE_ASSETS.ICON.KEY: + if (params[key].sizes && params[key].sizes.length > 0) { + assetObj = { + id: NATIVE_ASSETS.ICON.ID, + required: params[key].required ? 1 : 0, + img: { + type: NATIVE_ASSET_IMAGE_TYPE.ICON, + w: params[key].w || params[key].width || (params[key].sizes ? params[key].sizes[0] : UNDEFINED), + h: params[key].h || params[key].height || (params[key].sizes ? params[key].sizes[1] : UNDEFINED), + } + }; + } else { + _logWarn('Error: Icon sizes is required for native ad: ' + JSON.stringify(params)); + }; + break; + case NATIVE_ASSETS.VIDEO.KEY: + assetObj = { + id: NATIVE_ASSETS.VIDEO.ID, + required: params[key].required ? 1 : 0, + video: { + minduration: params[key].minduration, + maxduration: params[key].maxduration, + protocols: params[key].protocols, + mimes: params[key].mimes, + ext: params[key].ext + } + }; + break; + case NATIVE_ASSETS.EXT.KEY: + assetObj = { + id: NATIVE_ASSETS.EXT.ID, + required: params[key].required ? 1 : 0, + }; + break; + case NATIVE_ASSETS.LOGO.KEY: + assetObj = { + id: NATIVE_ASSETS.LOGO.ID, + required: params[key].required ? 1 : 0, + img: { + type: NATIVE_ASSET_IMAGE_TYPE.LOGO, + w: params[key].w || params[key].width || (params[key].sizes ? params[key].sizes[0] : UNDEFINED), + h: params[key].h || params[key].height || (params[key].sizes ? params[key].sizes[1] : UNDEFINED) + } + }; + break; + case NATIVE_ASSETS.SPONSOREDBY.KEY: + case NATIVE_ASSETS.BODY.KEY: + case NATIVE_ASSETS.RATING.KEY: + case NATIVE_ASSETS.LIKES.KEY: + case NATIVE_ASSETS.DOWNLOADS.KEY: + case NATIVE_ASSETS.PRICE.KEY: + case NATIVE_ASSETS.SALEPRICE.KEY: + case NATIVE_ASSETS.PHONE.KEY: + case NATIVE_ASSETS.ADDRESS.KEY: + case NATIVE_ASSETS.DESC2.KEY: + case NATIVE_ASSETS.DISPLAYURL.KEY: + case NATIVE_ASSETS.CTA.KEY: + assetObj = _commonNativeRequestObject(NATIVE_ASSET_KEY_TO_ASSET_MAP[key], params); + break; + } + } + } + if (assetObj && assetObj.id) { + nativeRequestObject.assets[nativeRequestObject.assets.length] = assetObj; + } + }; + + // for native image adtype prebid has to have few required assests i.e. title,sponsoredBy, image + // if any of these are missing from the request then request will not be sent + var requiredAssetCount = NATIVE_MINIMUM_REQUIRED_IMAGE_ASSETS.length; + var presentrequiredAssetCount = 0; + NATIVE_MINIMUM_REQUIRED_IMAGE_ASSETS.forEach(ele => { + var lengthOfExistingAssets = nativeRequestObject.assets.length; + for (var i = 0; i < lengthOfExistingAssets; i++) { + if (ele.id == nativeRequestObject.assets[i].id) { + presentrequiredAssetCount++; + break; + } + } + }); + if (requiredAssetCount == presentrequiredAssetCount) { + isInvalidNativeRequest = false; + } else { + isInvalidNativeRequest = true; + } + return nativeRequestObject; +} + +function _createBannerRequest(bid) { + var sizes = bid.mediaTypes.banner.sizes; + var format = []; + var bannerObj; + if (sizes !== UNDEFINED && utils.isArray(sizes)) { + bannerObj = {}; + if (!bid.params.width && !bid.params.height) { + if (sizes.length === 0) { + // i.e. since bid.params does not have width or height, and length of sizes is 0, need to ignore this banner imp + bannerObj = UNDEFINED; + _logWarn('Error: mediaTypes.banner.size missing for adunit: ' + bid.params.adUnit + '. Ignoring the banner impression in the adunit.'); + return bannerObj; + } else { + bannerObj.w = parseInt(sizes[0][0], 10); + bannerObj.h = parseInt(sizes[0][1], 10); + sizes = sizes.splice(1, sizes.length - 1); + } + } else { + bannerObj.w = bid.params.width; + bannerObj.h = bid.params.height; + } + if (sizes.length > 0) { + format = []; + sizes.forEach(function (size) { + if (size.length > 1) { + format.push({ w: size[0], h: size[1] }); + } + }); + if (format.length > 0) { + bannerObj.format = format; + } + } + bannerObj.pos = 0; + bannerObj.topframe = utils.inIframe() ? 0 : 1; + } else { + _logWarn('Error: mediaTypes.banner.size missing for adunit: ' + bid.params.adUnit + '. Ignoring the banner impression in the adunit.'); + bannerObj = UNDEFINED; + } + return bannerObj; +} + +// various error levels are not always used +// eslint-disable-next-line no-unused-vars +function _logMessage(textValue, objectValue) { + utils.logMessage('PubWise: ' + textValue, objectValue); +} + +// eslint-disable-next-line no-unused-vars +function _logInfo(textValue, objectValue) { + utils.logInfo('PubWise: ' + textValue, objectValue); +} + +// eslint-disable-next-line no-unused-vars +function _logWarn(textValue, objectValue) { + utils.logWarn('PubWise: ' + textValue, objectValue); +} + +// eslint-disable-next-line no-unused-vars +function _logError(textValue, objectValue) { + utils.logError('PubWise: ' + textValue, objectValue); +} + +// function _decorateLog() { +// arguments[0] = 'PubWise' + arguments[0]; +// return arguments +// } + +// these are exported only for testing so maintaining the JS convention of _ to indicate the intent +export { + _checkMediaType, + _parseAdSlot +} + +registerBidder(spec); diff --git a/modules/pubwiseBidAdapter.md b/modules/pubwiseBidAdapter.md new file mode 100644 index 00000000000..8cf38a63913 --- /dev/null +++ b/modules/pubwiseBidAdapter.md @@ -0,0 +1,78 @@ +# Overview + +``` +Module Name: PubWise Bid Adapter +Module Type: Bidder Adapter +Maintainer: info@pubwise.io +``` + +# Description + +Connects to PubWise exchange for bids. + +# Sample Banner Ad Unit: For Publishers + +With isTest parameter the system will respond in whatever dimensions provided. + +## Params + + + +## Banner +``` +var adUnits = [ + { + code: "div-gpt-ad-1460505748561-0", + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [{ + bidder: 'pubwise', + params: { + siteId: "xxxxxx", + isTest: true + } + }] + } +] +``` +## Native +``` +var adUnits = [ + { + code: 'div-gpt-ad-1460505748561-1', + sizes: [[1, 1]], + mediaTypes: { + native: { + title: { + required: true, + len: 80 + }, + body: { + required: true + }, + image: { + required: true, + sizes: [150, 50] + }, + sponsoredBy: { + required: true + }, + icon: { + required: false + } + } + }, + bids: [{ + bidder: 'pubwise', + params: { + siteId: "xxxxxx", + isTest: true, + }, + }] + } +] +``` + diff --git a/test/spec/modules/pubwiseBidAdapter_spec.js b/test/spec/modules/pubwiseBidAdapter_spec.js new file mode 100644 index 00000000000..450b028f6c7 --- /dev/null +++ b/test/spec/modules/pubwiseBidAdapter_spec.js @@ -0,0 +1,575 @@ +// import or require modules necessary for the test, e.g.: + +import {expect} from 'chai'; +import {spec} from 'modules/pubwiseBidAdapter.js'; +import {_checkMediaType} from 'modules/pubwiseBidAdapter.js'; // this is exported only for testing so maintaining the JS convention of _ to indicate the intent +import {_parseAdSlot} from 'modules/pubwiseBidAdapter.js'; // this is exported only for testing so maintaining the JS convention of _ to indicate the intent +import * as utils from 'src/utils.js'; + +const sampleRequestBanner = { + 'id': '6c148795eb836a', + 'tagid': 'div-gpt-ad-1460505748561-0', + 'bidfloor': 1, + 'secure': 1, + 'bidfloorcur': 'USD', + 'banner': { + 'w': 300, + 'h': 250, + 'format': [ + { + 'w': 300, + 'h': 600 + } + ], + 'pos': 0, + 'topframe': 1 + } +}; + +const sampleRequest = { + 'at': 1, + 'cur': [ + 'USD' + ], + 'imp': [ + sampleRequestBanner, + { + 'id': '7329ddc1d84eb3', + 'tagid': 'div-gpt-ad-1460505748561-1', + 'secure': 1, + 'bidfloorcur': 'USD', + 'native': { + 'request': '{"assets":[{"id":1,"required":1,"title":{"len":80}},{"id":5,"required":1,"data":{"type":2}},{"id":2,"required":1,"img":{"type":{"ID":2,"KEY":"image","TYPE":0},"w":150,"h":50}},{"id":4,"required":1,"data":{"type":1}}]}' + } + } + ], + 'site': { + 'page': 'http://localhost:9999/integrationExamples/gpt/hello_world.html?pbjs_debug=true', + 'ref': 'http://localhost:9999/integrationExamples/gpt/hello_world.html?pbjs_debug=true', + 'publisher': { + 'id': 'xxxxxx' + } + }, + 'device': { + 'ua': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/86.0.4240.198 Safari/537.36', + 'js': 1, + 'dnt': 0, + 'h': 600, + 'w': 800, + 'language': 'en-US', + 'geo': { + 'lat': 33.91989876432274, + 'lon': -84.38897708175764 + } + }, + 'user': { + 'gender': 'M', + 'geo': { + 'lat': 33.91989876432274, + 'lon': -84.38897708175764 + }, + 'yob': 2000 + }, + 'test': 0, + 'ext': { + 'version': '0.0.1' + }, + 'source': { + 'tid': '2c8cd034-f068-4419-8c30-f07292c0d17b' + } +}; + +const sampleValidBannerBidRequest = { + 'bidder': 'pubwise', + 'params': { + 'siteId': 'xxxxxx', + 'bidFloor': '1.00', + 'currency': 'USD', + 'gender': 'M', + 'lat': '33.91989876432274', + 'lon': '-84.38897708175764', + 'yob': '2000', + 'bcat': ['IAB25-3', 'IAB26-1', 'IAB26-2', 'IAB26-3', 'IAB26-4'], + }, + 'gdprConsent': { + 'consentString': 'BOEFEAyOEFEAyAHABDENAI4AAAB9vABAASA', + 'gdprApplies': 1, + }, + 'uspConsent': 1, + 'crumbs': { + 'pubcid': '9a62f261-3c0b-4cc8-8db3-a72ae86ec6ba' + }, + 'fpd': { + 'context': { + 'adServer': { + 'name': 'gam', + 'adSlot': '/19968336/header-bid-tag-0' + }, + 'pbAdSlot': '/19968336/header-bid-tag-0' + } + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ] + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': '2001a8b2-3bcf-417d-b64f-92641dae21e0', + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + 'bidId': '6c148795eb836a', + 'bidderRequestId': '18a45bff5ff705', + 'auctionId': '9f20663c-4629-4b5c-bff6-ff3aa8319358', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 +}; + +const sampleValidBidRequests = [ + sampleValidBannerBidRequest, + { + 'bidder': 'pubwise', + 'params': { + 'siteId': 'xxxxxx' + }, + 'crumbs': { + 'pubcid': '9a62f261-3c0b-4cc8-8db3-a72ae86ec6ba' + }, + 'nativeParams': { + 'title': { + 'required': true, + 'len': 80 + }, + 'body': { + 'required': true + }, + 'image': { + 'required': true, + 'sizes': [ + 150, + 50 + ] + }, + 'sponsoredBy': { + 'required': true + }, + 'icon': { + 'required': false + } + }, + 'fpd': { + 'context': { + 'adServer': { + 'name': 'gam', + 'adSlot': '/19968336/header-bid-tag-0' + }, + 'pbAdSlot': '/19968336/header-bid-tag-0' + } + }, + 'mediaTypes': { + 'native': { + 'title': { + 'required': true, + 'len': 80 + }, + 'body': { + 'required': true + }, + 'image': { + 'required': true, + 'sizes': [ + 150, + 50 + ] + }, + 'sponsoredBy': { + 'required': true + }, + 'icon': { + 'required': false + } + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-1', + 'transactionId': '2c8cd034-f068-4419-8c30-f07292c0d17b', + 'sizes': [], + 'bidId': '30ab7516a51a7c', + 'bidderRequestId': '18a45bff5ff705', + 'auctionId': '9f20663c-4629-4b5c-bff6-ff3aa8319358', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + } +] + +const sampleBidderBannerRequest = { + 'bidder': 'pubwise', + 'params': { + 'siteId': 'xxxxxx', + 'height': 250, + 'width': 300, + 'gender': 'M', + 'yob': '2000', + 'lat': '33.91989876432274', + 'lon': '-84.38897708175764', + 'bidFloor': '1.00', + 'currency': 'USD', + 'adSlot': '', + 'adUnit': '', + 'bcat': [ + 'IAB25-3', + 'IAB26-1', + 'IAB26-2', + 'IAB26-3', + 'IAB26-4', + ], + }, + 'crumbs': { + 'pubcid': '9a62f261-3c0b-4cc8-8db3-a72ae86ec6ba' + }, + 'fpd': { + 'context': { + 'adServer': { + 'name': 'gam', + 'adSlot': '/19968336/header-bid-tag-0' + }, + 'pbAdSlot': '/19968336/header-bid-tag-0' + } + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 600 + ] + ] + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': '2001a8b2-3bcf-417d-b64f-92641dae21e0', + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + 'bidId': '6c148795eb836a', + 'bidderRequestId': '18a45bff5ff705', + 'auctionId': '9f20663c-4629-4b5c-bff6-ff3aa8319358', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0, + 'gdprConsent': { + 'consentString': 'BOEFEAyOEFEAyAHABDENAI4AAAB9vABAASA', + 'gdprApplies': 1, + }, + 'uspConsent': 1, +}; + +const sampleBidderRequest = { + 'bidderCode': 'pubwise', + 'auctionId': '9f20663c-4629-4b5c-bff6-ff3aa8319358', + 'bidderRequestId': '18a45bff5ff705', + 'bids': [ + sampleBidderBannerRequest, + { + 'bidder': 'pubwise', + 'params': { + 'siteId': 'xxxxxx' + }, + 'crumbs': { + 'pubcid': '9a62f261-3c0b-4cc8-8db3-a72ae86ec6ba' + }, + 'nativeParams': { + 'title': { + 'required': true, + 'len': 80 + }, + 'body': { + 'required': true + }, + 'image': { + 'required': true, + 'sizes': [ + 150, + 50 + ] + }, + 'sponsoredBy': { + 'required': true + }, + 'icon': { + 'required': false + } + }, + 'fpd': { + 'context': { + 'adServer': { + 'name': 'gam', + 'adSlot': '/19968336/header-bid-tag-0' + }, + 'pbAdSlot': '/19968336/header-bid-tag-0' + } + }, + 'mediaTypes': { + 'native': { + 'title': { + 'required': true, + 'len': 80 + }, + 'body': { + 'required': true + }, + 'image': { + 'required': true, + 'sizes': [ + 150, + 50 + ] + }, + 'sponsoredBy': { + 'required': true + }, + 'icon': { + 'required': false + } + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-1', + 'transactionId': '2c8cd034-f068-4419-8c30-f07292c0d17b', + 'sizes': [], + 'bidId': '30ab7516a51a7c', + 'bidderRequestId': '18a45bff5ff705', + 'auctionId': '9f20663c-4629-4b5c-bff6-ff3aa8319358', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + } + ], + 'auctionStart': 1606269202001, + 'timeout': 1000, + 'gdprConsent': { + 'consentString': 'BOEFEAyOEFEAyAHABDENAI4AAAB9vABAASA', + 'gdprApplies': 1, + }, + 'uspConsent': 1, + 'refererInfo': { + 'referer': 'http://localhost:9999/integrationExamples/gpt/hello_world.html?pbjs_debug=true', + 'reachedTop': true, + 'isAmp': false, + 'numIframes': 0, + 'stack': [ + 'http://localhost:9999/integrationExamples/gpt/hello_world.html?pbjs_debug=true' + ], + 'canonicalUrl': null + }, + 'start': 1606269202004 +}; + +const sampleRTBResponse = { + 'body': { + 'id': '1606251348404', + 'seatbid': [ + { + 'bid': [ + { + 'id': '1606579704052', + 'impid': '6c148795eb836a', + 'price': 1.23, + 'adm': '\u003cdiv style="box-sizing: border-box;width:298px;height:248px;border: 1px solid rgba(0,0,0,.25);border-radius:10px;"\u003e\n\t\u003ch3 style="margin-top:80px;text-align: center;"\u003ePubWise Test Bid\u003c/h3\u003e\n\u003c/div\u003e', + 'crid': 'test', + 'w': 300, + 'h': 250 + }, + { + 'id': '1606579704052', + 'impid': '7329ddc1d84eb3', + 'price': 1.23, + 'adm': '{"ver":"1.2","assets":[{"id":1,"title":{"text":"PubWise Test"}},{"id":2,"img":{"type":3,"url":"http://www.pubwise.io","w":300,"h":250}},{"id":3,"img":{"type":1,"url":"http://www.pubwise.io","w":150,"h":125}},{"id":5,"data":{"type":2,"value":"PubWise Test Desc"}},{"id":4,"data":{"type":1,"value":"PubWise.io"}}],"link":{"url":"http://www.pubwise.io"}}', + 'crid': 'test', + 'w': 300, + 'h': 250 + } + ] + } + ], + 'bidid': 'testtesttest' + } +}; + +const samplePBBidObjects = [ + { + 'requestId': '6c148795eb836a', + 'cpm': '1.23', + 'width': 300, + 'height': 250, + 'creativeId': 'test', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 300, + 'ad': '
\n\t

PubWise Test Bid

\n
', + 'pw_seat': null, + 'pw_dspid': null, + 'partnerImpId': '1606579704052', + 'meta': {}, + 'mediaType': 'banner', + }, + { + 'requestId': '7329ddc1d84eb3', + 'cpm': '1.23', + 'width': 300, + 'height': 250, + 'creativeId': 'test', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 300, + 'ad': '{\"ver\":\"1.2\",\"assets\":[{\"id\":1,\"title\":{\"text\":\"PubWise Test\"}},{\"id\":2,\"img\":{\"type\":3,\"url\":\"http://www.pubwise.io\",\"w\":300,\"h\":250}},{\"id\":3,\"img\":{\"type\":1,\"url\":\"http://www.pubwise.io\",\"w\":150,\"h\":125}},{\"id\":5,\"data\":{\"type\":2,\"value\":\"PubWise Test Desc\"}},{\"id\":4,\"data\":{\"type\":1,\"value\":\"PubWise.io\"}}],\"link\":{\"url\":\"http://www.pubwise.io\"}}', + 'pw_seat': null, + 'pw_dspid': null, + 'partnerImpId': '1606579704052', + 'mediaType': 'native', + 'native': { + 'body': 'PubWise Test Desc', + 'icon': { + 'height': 125, + 'url': 'http://www.pubwise.io', + 'width': 150, + }, + 'image': { + 'height': 250, + 'url': 'http://www.pubwise.io', + 'width': 300, + }, + 'sponsoredBy': 'PubWise.io', + 'title': 'PubWise Test' + }, + 'meta': {}, + 'impressionTrackers': [], + 'jstracker': [], + 'clickTrackers': [], + 'clickUrl': 'http://www.pubwise.io' + } +]; + +describe('PubWiseAdapter', function () { + describe('Properly Validates Bids', function () { + it('valid bid', function () { + let validBid = { + bidder: 'pubwise', + params: { + siteId: 'xxxxxx' + } + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(true); + }); + + it('valid bid: extra fields are ok', function () { + let validBid = { + bidder: 'pubwise', + params: { + siteId: 'xxxxxx', + gender: 'M', + } + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(true); + }); + + it('invalid bid: no siteId', function () { + let inValidBid = { + bidder: 'pubwise', + params: { + gender: 'M', + } + }, + isValid = spec.isBidRequestValid(inValidBid); + expect(isValid).to.equal(false); + }); + + it('invalid bid: siteId should be a string', function () { + let validBid = { + bidder: 'pubwise', + params: { + siteId: 123456 + } + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(false); + }); + }); + + describe('Handling Request Construction', function () { + it('bid requests are not mutable', function() { + let sourceBidRequest = utils.deepClone(sampleValidBidRequests) + spec.buildRequests(sampleValidBidRequests, {auctinId: 'placeholder'}); + expect(sampleValidBidRequests).to.deep.equal(sourceBidRequest, 'Should be unedited as they are used elsewhere'); + }); + it('should handle complex bidRequest', function() { + let request = spec.buildRequests(sampleValidBidRequests, sampleBidderRequest); + expect(request.bidderRequest).to.equal(sampleBidderRequest); + }); + it('must conform to API for buildRequests', function() { + let request = spec.buildRequests(sampleValidBidRequests); + expect(request.bidderRequest).to.be.undefined; + }); + }); + + describe('Identifies Media Types', function () { + it('identifies native adm type', function() { + let adm = '{"ver":"1.2","assets":[{"title":{"text":"PubWise Test"}},{"img":{"type":3,"url":"http://www.pubwise.io"}},{"img":{"type":1,"url":"http://www.pubwise.io"}},{"data":{"type":2,"value":"PubWise Test Desc"}},{"data":{"type":1,"value":"PubWise.io"}}],"link":{"url":""}}'; + let newBid = {mediaType: 'unknown'}; + _checkMediaType(adm, newBid); + expect(newBid.mediaType).to.equal('native', adm + ' Is a Native adm'); + }); + + it('identifies banner adm type', function() { + let adm = '

PubWise Test Bid

'; + let newBid = {mediaType: 'unknown'}; + _checkMediaType(adm, newBid); + expect(newBid.mediaType).to.equal('banner', adm + ' Is a Banner adm'); + }); + }); + + describe('Properly Parses AdSlot Data', function () { + it('parses banner', function() { + let testBid = utils.deepClone(sampleValidBannerBidRequest) + _parseAdSlot(testBid) + expect(testBid).to.deep.equal(sampleBidderBannerRequest); + }); + }); + + describe('Properly Handles Response', function () { + it('handles response with muiltiple responses', function() { + // the request when it comes back is on the data object + let pbResponse = spec.interpretResponse(sampleRTBResponse, {'data': sampleRequest}) + expect(pbResponse).to.deep.equal(samplePBBidObjects); + }); + }); +}); From a95f1db0e11b416a4885fc59aae1970053c9a30d Mon Sep 17 00:00:00 2001 From: cpuBird <54024689+cpuBird@users.noreply.github.com> Date: Wed, 9 Dec 2020 21:24:30 +0530 Subject: [PATCH 091/103] Vdoai adapter update - Added video mediaType support (#5970) * added video mediatype support to vdoai adapter * added unit test --- modules/vdoaiBidAdapter.js | 16 +++++++--- modules/vdoaiBidAdapter.md | 23 +++++++++++++++ test/spec/modules/vdoaiBidAdapter_spec.js | 36 +++++++++++++++++++++++ 3 files changed, 71 insertions(+), 4 deletions(-) diff --git a/modules/vdoaiBidAdapter.js b/modules/vdoaiBidAdapter.js index 395953fb737..8cfcd67bd00 100644 --- a/modules/vdoaiBidAdapter.js +++ b/modules/vdoaiBidAdapter.js @@ -1,14 +1,14 @@ import * as utils from '../src/utils.js'; import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {BANNER} from '../src/mediaTypes.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; const BIDDER_CODE = 'vdo.ai'; const ENDPOINT_URL = 'https://prebid.vdo.ai/auction'; export const spec = { code: BIDDER_CODE, - supportedMediaTypes: [BANNER], + supportedMediaTypes: [BANNER, VIDEO], /** * Determines whether or not the given bid request is valid. * @@ -40,7 +40,8 @@ export const spec = { height: height, bidId: bidRequest.bidId, referer: bidderRequest.refererInfo.referer, - id: bidRequest.auctionId + id: bidRequest.auctionId, + mediaType: bidRequest.mediaTypes.video ? 'video' : 'banner' }; bidRequest.params.bidFloor && (payload['bidFloor'] = bidRequest.params.bidFloor); return { @@ -90,8 +91,15 @@ export const spec = { ttl: config.getConfig('_bidderTimeout'), // referrer: referrer, // ad: response.adm - ad: adCreative + // ad: adCreative, + mediaType: response.mediaType }; + + if (response.mediaType == 'video') { + bidResponse.vastXml = adCreative; + } else { + bidResponse.ad = adCreative; + } bidResponses.push(bidResponse); } return bidResponses; diff --git a/modules/vdoaiBidAdapter.md b/modules/vdoaiBidAdapter.md index 81bd8e69c1d..712adc0ec76 100644 --- a/modules/vdoaiBidAdapter.md +++ b/modules/vdoaiBidAdapter.md @@ -31,4 +31,27 @@ Module that connects to VDO.AI's demand sources ] } ]; +``` + + +# Video Test Parameters +``` +var videoAdUnit = { + code: 'test-div', + sizes: [[640, 480]], + mediaTypes: { + video: { + playerSize: [[640, 480]], + context: 'instream' + }, + }, + bids: [ + { + bidder: "vdo.ai", + params: { + placement: 'newsdv77' + } + } + ] +}; ``` \ No newline at end of file diff --git a/test/spec/modules/vdoaiBidAdapter_spec.js b/test/spec/modules/vdoaiBidAdapter_spec.js index c9d826d8dc8..5318bb43eca 100644 --- a/test/spec/modules/vdoaiBidAdapter_spec.js +++ b/test/spec/modules/vdoaiBidAdapter_spec.js @@ -37,6 +37,7 @@ describe('vdoaiBidAdapter', function () { 'bidId': '23beaa6af6cdde', 'bidderRequestId': '19c0c1efdf37e7', 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf1', + 'mediaTypes': 'banner' } ]; @@ -101,5 +102,40 @@ describe('vdoaiBidAdapter', function () { let result = spec.interpretResponse(serverResponse, bidRequest[0]); expect(Object.keys(result)).to.deep.equal(Object.keys(expectedResponse)); }); + + it('handles instream video responses', function () { + let serverResponse = { + body: { + 'vdoCreative': '', + 'price': 4.2, + 'adid': '12345asdfg', + 'currency': 'EUR', + 'statusMessage': 'Bid available', + 'requestId': 'bidId123', + 'width': 300, + 'height': 250, + 'netRevenue': true, + 'mediaType': 'video' + } + }; + let bidRequest = [ + { + 'method': 'POST', + 'url': ENDPOINT_URL, + 'data': { + 'placementId': 'testPlacementId', + 'width': '300', + 'height': '200', + 'bidId': 'bidId123', + 'referer': 'www.example.com', + 'mediaType': 'video' + } + } + ]; + + let result = spec.interpretResponse(serverResponse, bidRequest[0]); + expect(result[0]).to.have.property('vastXml'); + expect(result[0]).to.have.property('mediaType', 'video'); + }); }); }); From c0af43297cc4ba8e89ca2d238e6469565850ff42 Mon Sep 17 00:00:00 2001 From: mmoschovas <63253416+mmoschovas@users.noreply.github.com> Date: Wed, 9 Dec 2020 13:54:26 -0500 Subject: [PATCH 092/103] Price Floors update to include modelTimestamp displaying when the floors file was produced (#6061) Rubicon Anaytics Update to pass modelTimestamp if exists --- modules/priceFloors.js | 1 + modules/rubiconAnalyticsAdapter.js | 1 + test/spec/modules/priceFloors_spec.js | 23 +++++++++++++++++++ .../modules/rubiconAnalyticsAdapter_spec.js | 3 +++ 4 files changed, 28 insertions(+) diff --git a/modules/priceFloors.js b/modules/priceFloors.js index fd8a46b172f..c0797f710de 100644 --- a/modules/priceFloors.js +++ b/modules/priceFloors.js @@ -291,6 +291,7 @@ export function updateAdUnitsForAuction(adUnits, floorData, auctionId) { skipRate: floorData.skipRate, floorMin: floorData.floorMin, modelVersion: utils.deepAccess(floorData, 'data.modelVersion'), + modelTimestamp: utils.deepAccess(floorData, 'data.modelTimestamp'), location: utils.deepAccess(floorData, 'data.location', 'noData'), floorProvider: floorData.floorProvider, fetchStatus: _floorsConfig.fetchStatus diff --git a/modules/rubiconAnalyticsAdapter.js b/modules/rubiconAnalyticsAdapter.js index ff8cb7895b9..ad78c601ab6 100644 --- a/modules/rubiconAnalyticsAdapter.js +++ b/modules/rubiconAnalyticsAdapter.js @@ -237,6 +237,7 @@ function sendMessage(auctionId, bidWonId) { auction.floors = utils.pick(auctionCache.floorData, [ 'location', 'modelVersion as modelName', + 'modelTimestamp', 'skipped', 'enforcement', () => utils.deepAccess(auctionCache.floorData, 'enforcements.enforceJS'), 'dealsEnforced', () => utils.deepAccess(auctionCache.floorData, 'enforcements.floorDeals'), diff --git a/test/spec/modules/priceFloors_spec.js b/test/spec/modules/priceFloors_spec.js index 8c673d29701..1b3ce021068 100644 --- a/test/spec/modules/priceFloors_spec.js +++ b/test/spec/modules/priceFloors_spec.js @@ -23,6 +23,7 @@ describe('the price floors module', function () { let clock; const basicFloorData = { modelVersion: 'basic model', + modelTimestamp: 1606772895, currency: 'USD', schema: { delimiter: '|', @@ -184,6 +185,7 @@ describe('the price floors module', function () { let resultingData = getFloorsDataForAuction(inputFloorData, 'test_div_1'); expect(resultingData).to.deep.equal({ modelVersion: 'basic model', + modelTimestamp: 1606772895, currency: 'USD', schema: { delimiter: '|', @@ -201,6 +203,7 @@ describe('the price floors module', function () { resultingData = getFloorsDataForAuction(inputFloorData, 'this_is_a_div'); expect(resultingData).to.deep.equal({ modelVersion: 'basic model', + modelTimestamp: 1606772895, currency: 'USD', schema: { delimiter: '^', @@ -429,6 +432,7 @@ describe('the price floors module', function () { skipped: true, floorMin: undefined, modelVersion: undefined, + modelTimestamp: undefined, location: 'noData', skipRate: 0, fetchStatus: undefined, @@ -463,6 +467,7 @@ describe('the price floors module', function () { skipped: false, floorMin: undefined, modelVersion: 'adUnit Model Version', + modelTimestamp: 1606772895, location: 'adUnit', skipRate: 0, fetchStatus: undefined, @@ -496,6 +501,7 @@ describe('the price floors module', function () { validateBidRequests(true, { skipped: false, modelVersion: 'adUnit Model Version', + modelTimestamp: 1606772895, location: 'adUnit', skipRate: 0, floorMin: 7, @@ -510,6 +516,7 @@ describe('the price floors module', function () { skipped: false, floorMin: undefined, modelVersion: 'basic model', + modelTimestamp: 1606772895, location: 'setConfig', skipRate: 0, fetchStatus: undefined, @@ -531,6 +538,7 @@ describe('the price floors module', function () { skipped: false, floorMin: undefined, modelVersion: 'basic model', + modelTimestamp: 1606772895, location: 'setConfig', skipRate: 0, fetchStatus: undefined, @@ -545,6 +553,7 @@ describe('the price floors module', function () { skipped: false, floorMin: undefined, modelVersion: 'basic model', + modelTimestamp: 1606772895, location: 'setConfig', skipRate: 0, fetchStatus: undefined, @@ -559,6 +568,7 @@ describe('the price floors module', function () { skipped: false, floorMin: undefined, modelVersion: 'basic model', + modelTimestamp: 1606772895, location: 'setConfig', skipRate: 0, fetchStatus: undefined, @@ -582,6 +592,7 @@ describe('the price floors module', function () { skipped: false, floorMin: undefined, modelVersion: 'basic model', + modelTimestamp: 1606772895, location: 'setConfig', skipRate: 50, fetchStatus: undefined, @@ -596,6 +607,7 @@ describe('the price floors module', function () { skipped: false, floorMin: undefined, modelVersion: 'basic model', + modelTimestamp: 1606772895, location: 'setConfig', skipRate: 10, fetchStatus: undefined, @@ -610,6 +622,7 @@ describe('the price floors module', function () { skipped: false, floorMin: undefined, modelVersion: 'basic model', + modelTimestamp: 1606772895, location: 'setConfig', skipRate: 0, fetchStatus: undefined, @@ -674,6 +687,7 @@ describe('the price floors module', function () { skipped: false, floorMin: undefined, modelVersion: 'model-1', + modelTimestamp: undefined, location: 'setConfig', skipRate: 0, fetchStatus: undefined, @@ -687,6 +701,7 @@ describe('the price floors module', function () { skipped: false, floorMin: undefined, modelVersion: 'model-2', + modelTimestamp: undefined, location: 'setConfig', skipRate: 0, fetchStatus: undefined, @@ -700,6 +715,7 @@ describe('the price floors module', function () { skipped: false, floorMin: undefined, modelVersion: 'model-3', + modelTimestamp: undefined, location: 'setConfig', skipRate: 0, fetchStatus: undefined, @@ -729,6 +745,7 @@ describe('the price floors module', function () { skipped: false, floorMin: undefined, modelVersion: 'basic model', + modelTimestamp: 1606772895, location: 'setConfig', skipRate: 0, fetchStatus: undefined, @@ -808,6 +825,7 @@ describe('the price floors module', function () { skipped: false, floorMin: undefined, modelVersion: 'basic model', + modelTimestamp: 1606772895, location: 'setConfig', skipRate: 0, fetchStatus: 'timeout', @@ -846,6 +864,7 @@ describe('the price floors module', function () { skipped: false, floorMin: undefined, modelVersion: 'fetch model name', + modelTimestamp: 1606772895, location: 'fetch', skipRate: 0, fetchStatus: 'success', @@ -883,6 +902,7 @@ describe('the price floors module', function () { skipped: false, floorMin: undefined, modelVersion: 'fetch model name', + modelTimestamp: 1606772895, location: 'fetch', skipRate: 0, fetchStatus: 'success', @@ -923,6 +943,7 @@ describe('the price floors module', function () { skipped: false, floorMin: undefined, modelVersion: 'fetch model name', + modelTimestamp: 1606772895, location: 'fetch', skipRate: 95, fetchStatus: 'success', @@ -945,6 +966,7 @@ describe('the price floors module', function () { skipped: false, floorMin: undefined, modelVersion: 'basic model', + modelTimestamp: 1606772895, location: 'setConfig', skipRate: 0, fetchStatus: 'error', @@ -969,6 +991,7 @@ describe('the price floors module', function () { skipped: false, floorMin: undefined, modelVersion: 'basic model', + modelTimestamp: 1606772895, location: 'setConfig', skipRate: 0, fetchStatus: 'success', diff --git a/test/spec/modules/rubiconAnalyticsAdapter_spec.js b/test/spec/modules/rubiconAnalyticsAdapter_spec.js index 4891b8d3282..0d6cf331e52 100644 --- a/test/spec/modules/rubiconAnalyticsAdapter_spec.js +++ b/test/spec/modules/rubiconAnalyticsAdapter_spec.js @@ -852,6 +852,7 @@ describe('rubicon analytics adapter', function () { auctionInit.bidderRequests[0].bids[0].floorData = { skipped: false, modelVersion: 'someModelName', + modelTimestamp: 1606772895, location: 'setConfig', skipRate: 15, fetchStatus: 'error', @@ -953,6 +954,7 @@ describe('rubicon analytics adapter', function () { expect(message.auctions[0].floors).to.deep.equal({ location: 'setConfig', modelName: 'someModelName', + modelTimestamp: 1606772895, skipped: false, enforcement: true, dealsEnforced: false, @@ -998,6 +1000,7 @@ describe('rubicon analytics adapter', function () { expect(message.auctions[0].floors).to.deep.equal({ location: 'setConfig', modelName: 'someModelName', + modelTimestamp: 1606772895, skipped: false, enforcement: true, dealsEnforced: false, From a17c2346c233c301b948c5b5b69ad1d30a26c53a Mon Sep 17 00:00:00 2001 From: Olivier Date: Wed, 9 Dec 2020 20:26:28 +0100 Subject: [PATCH 093/103] Adagio Bid Adapter: hotfix - detect support for intersectionObserver (#6095) * Detect support for intersectionObserver * Fix IE11 * Add special params for non standard integration * Stronger IntersectionObserver detection * Fix test and add new params to .md file --- modules/adagioBidAdapter.js | 19 +++++++++-- modules/adagioBidAdapter.md | 4 +++ test/spec/modules/adagioBidAdapter_spec.js | 37 ++++++++++++++++++++-- 3 files changed, 54 insertions(+), 6 deletions(-) diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index cab233d5387..6f7feec59c9 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -495,6 +495,12 @@ function autoDetectEnvironment() { return environment; }; +function supportIObs() { + const currentWindow = internal.getCurrentWindow(); + return !!(currentWindow && currentWindow.IntersectionObserver && currentWindow.IntersectionObserverEntry && + currentWindow.IntersectionObserverEntry.prototype && 'intersectionRatio' in currentWindow.IntersectionObserverEntry.prototype); +} + function getFeatures(bidRequest, bidderRequest) { const { adUnitCode, params } = bidRequest; const { adUnitElementId } = params; @@ -569,6 +575,7 @@ export const internal = { getRefererInfo, adagioScriptFromLocalStorageCb, getCurrentWindow, + supportIObs, canAccessTopWindow, isRendererPreferredFromPublisher }; @@ -692,15 +699,21 @@ export const spec = { return false; } - const { organizationId, site, placement } = params; - const adUnitElementId = params.adUnitElementId || internal.autoDetectAdUnitElementId(adUnitCode); + const { organizationId, site } = params; + const adUnitElementId = (params.useAdUnitCodeAsAdUnitElementId === true) + ? adUnitCode + : params.adUnitElementId || internal.autoDetectAdUnitElementId(adUnitCode); + const placement = (params.useAdUnitCodeAsPlacement === true) ? adUnitCode : params.placement; const environment = params.environment || internal.autoDetectEnvironment(); + const supportIObs = internal.supportIObs(); // insure auto-detected params are kept in `bid` object. bid.params = { ...params, adUnitElementId, - environment + environment, + placement, + supportIObs }; const debugData = () => ({ diff --git a/modules/adagioBidAdapter.md b/modules/adagioBidAdapter.md index c55a24f1115..aa79338d79e 100644 --- a/modules/adagioBidAdapter.md +++ b/modules/adagioBidAdapter.md @@ -38,6 +38,8 @@ Connects to Adagio demand source to fetch bids. category: 'sport', // Recommended. Category of the content displayed in the page. subcategory: 'handball', // Optional. Subcategory of the content displayed in the page. postBid: false, // Optional. Use it in case of Post-bid integration only. + useAdUnitCodeAsAdUnitElementId: false // Optional. Use it by-pass adUnitElementId and use the adUnit code as value + useAdUnitCodeAsPlacement: false // Optional. Use it to by-pass placement and use the adUnit code as value // Optional debug mode, used to get a bid response with expected cpm. debug: { enabled: true, @@ -76,6 +78,8 @@ Connects to Adagio demand source to fetch bids. category: 'sport', // Recommended. Category of the content displayed in the page. subcategory: 'handball', // Optional. Subcategory of the content displayed in the page. postBid: false, // Optional. Use it in case of Post-bid integration only. + useAdUnitCodeAsAdUnitElementId: false // Optional. Use it by-pass adUnitElementId and use the adUnit code as value + useAdUnitCodeAsPlacement: false // Optional. Use it to by-pass placement and use the adUnit code as value video: { skip: 0 // OpenRTB 2.5 video options defined here override ones defined in mediaTypes. diff --git a/test/spec/modules/adagioBidAdapter_spec.js b/test/spec/modules/adagioBidAdapter_spec.js index 2cf97a1129b..0a585caaa1a 100644 --- a/test/spec/modules/adagioBidAdapter_spec.js +++ b/test/spec/modules/adagioBidAdapter_spec.js @@ -144,6 +144,19 @@ describe('Adagio bid adapter', () => { sinon.assert.callCount(utils.logWarn, 1); }); + it('should use adUnit code for adUnitElementId and placement params', function() { + const bid01 = new BidRequestBuilder({ params: { + organizationId: '1000', + site: 'site-name', + useAdUnitCodeAsPlacement: true, + useAdUnitCodeAsAdUnitElementId: true + }}).build(); + + expect(spec.isBidRequestValid(bid01)).to.equal(true); + expect(bid01.params.adUnitElementId).to.equal('adunit-code'); + expect(bid01.params.placement).to.equal('adunit-code'); + }) + it('should return false when a required param is missing', function() { const bid01 = new BidRequestBuilder({ params: { organizationId: '1000', @@ -229,7 +242,8 @@ describe('Adagio bid adapter', () => { placement: 'PAVE_ATF', site: 'SITE-NAME', adUnitElementId: 'gpt-adunit-code', - environment: 'desktop' + environment: 'desktop', + supportIObs: true } }], auctionId: '4fd1ca2d-846c-4211-b9e5-321dfe1709c9', @@ -249,7 +263,8 @@ describe('Adagio bid adapter', () => { placement: 'PAVE_ATF', site: 'SITE-NAME', adUnitElementId: 'gpt-adunit-code', - environment: 'desktop' + environment: 'desktop', + supportIObs: true } }], auctionId: '4fd1ca2d-846c-4211-b9e5-321dfe1709c9', @@ -260,6 +275,7 @@ describe('Adagio bid adapter', () => { it('should store bids config once by bid in window.top if it accessible', function() { sandbox.stub(adagio, 'getCurrentWindow').returns(window.top); + sandbox.stub(adagio, 'supportIObs').returns(true); // replace by the values defined in beforeEach window.top.ADAGIO = { @@ -274,8 +290,22 @@ describe('Adagio bid adapter', () => { expect(find(window.top.ADAGIO.pbjsAdUnits, aU => aU.code === 'adunit-code-02')).to.deep.eql(expected[1]); }); + it('should detect IntersectionObserver support', function() { + sandbox.stub(adagio, 'getCurrentWindow').returns(window.top); + sandbox.stub(adagio, 'supportIObs').returns(false); + + window.top.ADAGIO = { + ...window.ADAGIO + }; + + spec.isBidRequestValid(bid01); + const validBidReq = find(window.top.ADAGIO.pbjsAdUnits, aU => aU.code === 'adunit-code-01'); + expect(validBidReq.bids[0].params.supportIObs).to.equal(false); + }); + it('should store bids config once by bid in current window', function() { sandbox.stub(adagio, 'getCurrentWindow').returns(window.self); + sandbox.stub(adagio, 'supportIObs').returns(true); spec.isBidRequestValid(bid01); spec.isBidRequestValid(bid02); @@ -740,7 +770,8 @@ describe('Adagio bid adapter', () => { pagetype: 'ARTICLE', category: 'NEWS', subcategory: 'SPORT', - environment: 'desktop' + environment: 'desktop', + supportIObs: true }, adUnitCode: 'adunit-code', mediaTypes: { From 83bafc0475a68033f6e0d9d1fe9e7014cea5ead8 Mon Sep 17 00:00:00 2001 From: Gena Date: Wed, 9 Dec 2020 22:11:58 +0200 Subject: [PATCH 094/103] Adt new alias (#6004) * Add new alias * Add new aliases and fix endpoints * lint * Fix tests --- modules/adtelligentBidAdapter.js | 16 ++++++++++++---- test/spec/modules/adtelligentBidAdapter_spec.js | 17 +++++++++++------ 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/modules/adtelligentBidAdapter.js b/modules/adtelligentBidAdapter.js index 51138a2cac7..b2e37767a1e 100644 --- a/modules/adtelligentBidAdapter.js +++ b/modules/adtelligentBidAdapter.js @@ -12,15 +12,23 @@ const HOST_GETTERS = { default: (function () { let num = 0; return function () { - return 'ghb' + subdomainSuffixes[num++ % subdomainSuffixes.length] + '.adtelligent.com' + return 'ghb' + subdomainSuffixes[num++ % subdomainSuffixes.length] + '.adtelligent.com'; } }()), appaloosa: function () { - return 'hb.appaloosa.media' + return 'ghb.hb.appaloosa.media'; + }, + onefiftytwomedia: function() { + return 'ghb.ads.152media.com'; + }, + mediafuse: function() { + return 'ghb.hbmp.mediafuse.com'; } + } const getUri = function (bidderCode) { - let getter = HOST_GETTERS[bidderCode] || HOST_GETTERS['default']; + let bidderWithoutSuffix = bidderCode.split('_')[0]; + let getter = HOST_GETTERS[bidderWithoutSuffix] || HOST_GETTERS['default']; return PROTOCOL + getter() + AUCTION_PATH } const OUTSTREAM_SRC = 'https://player.adtelligent.com/outstream-unit/2.01/outstream.min.js'; @@ -32,7 +40,7 @@ const syncsCache = {}; export const spec = { code: BIDDER_CODE, gvlid: 410, - aliases: ['onefiftytwomedia', 'selectmedia', 'appaloosa'], + aliases: ['onefiftytwomedia', 'selectmedia', 'appaloosa', 'mediafuse'], supportedMediaTypes: [VIDEO, BANNER], isBidRequestValid: function (bid) { return !!utils.deepAccess(bid, 'params.aid'); diff --git a/test/spec/modules/adtelligentBidAdapter_spec.js b/test/spec/modules/adtelligentBidAdapter_spec.js index 62449771416..5c1e4a38d03 100644 --- a/test/spec/modules/adtelligentBidAdapter_spec.js +++ b/test/spec/modules/adtelligentBidAdapter_spec.js @@ -11,8 +11,13 @@ const EXPECTED_ENDPOINTS = [ 'https://ghb.adtelligent.com/v2/auction/' ]; const aliasEP = { - appaloosa: 'https://hb.appaloosa.media/v2/auction/' + appaloosa: 'https://ghb.hb.appaloosa.media/v2/auction/', + appaloosa_publisherSuffix: 'https://ghb.hb.appaloosa.media/v2/auction/', + onefiftytwomedia: 'https://ghb.ads.152media.com/v2/auction/', + mediafuse: 'https://ghb.hbmp.mediafuse.com/v2/auction/' }; + +const DEFAULT_ADATPER_REQ = { bidderCode: 'adtelligent' }; const DISPLAY_REQUEST = { 'bidder': 'adtelligent', 'params': { @@ -244,10 +249,10 @@ describe('adtelligentBidAdapter', () => { let videoBidRequests = [VIDEO_REQUEST]; let displayBidRequests = [DISPLAY_REQUEST]; let videoAndDisplayBidRequests = [DISPLAY_REQUEST, VIDEO_REQUEST]; - const displayRequest = spec.buildRequests(displayBidRequests, {}); - const videoRequest = spec.buildRequests(videoBidRequests, {}); - const videoAndDisplayRequests = spec.buildRequests(videoAndDisplayBidRequests, {}); - const rotatingRequest = spec.buildRequests(displayBidRequests, {}); + const displayRequest = spec.buildRequests(displayBidRequests, DEFAULT_ADATPER_REQ); + const videoRequest = spec.buildRequests(videoBidRequests, DEFAULT_ADATPER_REQ); + const videoAndDisplayRequests = spec.buildRequests(videoAndDisplayBidRequests, DEFAULT_ADATPER_REQ); + const rotatingRequest = spec.buildRequests(displayBidRequests, DEFAULT_ADATPER_REQ); it('rotates endpoints', () => { const bidReqUrls = [displayRequest[0], videoRequest[0], videoAndDisplayRequests[0], rotatingRequest[0]].map(br => br.url); expect(bidReqUrls).to.deep.equal(EXPECTED_ENDPOINTS); @@ -276,7 +281,7 @@ describe('adtelligentBidAdapter', () => { expect(videoAndDisplayRequests.every(comparator)).to.be.true; }); it('forms correct ADPOD request', () => { - const pbBidReqData = spec.buildRequests([ADPOD_REQUEST], {})[0].data; + const pbBidReqData = spec.buildRequests([ADPOD_REQUEST], DEFAULT_ADATPER_REQ)[0].data; const impRequest = pbBidReqData.BidRequests[0] expect(impRequest.AdType).to.be.equal('video'); expect(impRequest.Adpod).to.be.a('object'); From 09601b65a6a82aca419afcc618bd798cf429aa22 Mon Sep 17 00:00:00 2001 From: Mike Chowla Date: Wed, 9 Dec 2020 12:49:22 -0800 Subject: [PATCH 095/103] Prebid 4.9.0 Release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ed4514cee42..5c82e724453 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.19.0-pre", + "version": "4.19.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 3496b3f627202fedca7a4a42d97407b819d6c014 Mon Sep 17 00:00:00 2001 From: Mike Chowla Date: Wed, 9 Dec 2020 13:14:51 -0800 Subject: [PATCH 096/103] Increment pre version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5c82e724453..04ec495c93d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.19.0", + "version": "4.20.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From f7a91ca2e00c2530d7a4a8fbaa959aab8e455b26 Mon Sep 17 00:00:00 2001 From: Stephen Johnston Date: Wed, 9 Dec 2020 17:54:33 -0500 Subject: [PATCH 097/103] Add Release Drafter Instructions to PR_REVIEW.md (#6085) * Add Release Drafter Instructions to PR_REVIEW.md * Add Prettier Formatting Makes keywords more distinct. Co-authored-by: Scott Menzer Co-authored-by: Scott Menzer --- PR_REVIEW.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/PR_REVIEW.md b/PR_REVIEW.md index f991a0254f5..662a1a871c8 100644 --- a/PR_REVIEW.md +++ b/PR_REVIEW.md @@ -18,7 +18,10 @@ For modules and core platform updates, the initial reviewer should request an ad - If the change results in needing updates to docs (such as public API change, module interface etc), add a label for "needs docs" and inform the submitter they must submit a docs PR to update the appropriate area of Prebid.org **before the PR can merge**. Help them with finding where the docs are located on prebid.org if needed. - If all above is good, add a `LGTM` comment and, if the change is in PBS-core or is an important module like the prebidServerBidAdapter, request 1 additional core member to review. - Once there are 2 `LGTM` on the PR, merge to master -- Add a line into the [draft release](https://github.com/prebid/Prebid.js/releases) notes for this submission. If no draft release is available, create one using [this template]( https://gist.github.com/mkendall07/c3af6f4691bed8a46738b3675cb5a479) +- The [draft release](https://github.com/prebid/Prebid.js/releases) notes are managed by [release drafter](https://github.com/release-drafter/release-drafter). To get the PR added to the release notes do the steps below. A github action will use that information to build the release notes. + - Adjust the PR Title to be appropriate for release notes + - Add a label for `feature`, `maintenance`, `fix`, `bugfix` or `bug` to categorize the PR + - Add a semver label of `major`, `minor` or `patch` to indicate the scope of change ### Reviewing a New or Updated Bid Adapter Documentation they're supposed to be following is https://docs.prebid.org/dev-docs/bidder-adaptor.html From 03b8213de8bede7883323fff6f3ccf00bad08410 Mon Sep 17 00:00:00 2001 From: olafbuitelaar Date: Thu, 10 Dec 2020 10:36:45 +0100 Subject: [PATCH 098/103] fix typo in PREVENT_WRITING_ON_MAIN_DOCUMENT (#6102) --- src/constants.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/constants.json b/src/constants.json index 7c0af445cdb..7e82946b65c 100644 --- a/src/constants.json +++ b/src/constants.json @@ -41,7 +41,7 @@ "AUCTION_DEBUG": "auctionDebug" }, "AD_RENDER_FAILED_REASON" : { - "PREVENT_WRITING_ON_MAIN_DOCUMENT": "preventWritingOnMainDocuemnt", + "PREVENT_WRITING_ON_MAIN_DOCUMENT": "preventWritingOnMainDocument", "NO_AD": "noAd", "EXCEPTION": "exception", "CANNOT_FIND_AD": "cannotFindAd", From 754ce678b47b8e13148e0a02a9db0d477ce3eaa6 Mon Sep 17 00:00:00 2001 From: jsnellbaker <31102355+jsnellbaker@users.noreply.github.com> Date: Thu, 10 Dec 2020 06:10:25 -0500 Subject: [PATCH 099/103] appnexusBidAdapter - update segment param logic (#6103) --- modules/appnexusBidAdapter.js | 14 +++++++++++++- test/spec/modules/appnexusBidAdapter_spec.js | 2 ++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index 203835db611..c102bee4e58 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -107,7 +107,19 @@ export const spec = { .filter(param => includes(USER_PARAMS, param)) .forEach((param) => { let uparam = utils.convertCamelToUnderscore(param); - userObj[uparam] = userObjBid.params.user[param] + if (param === 'segments' && utils.isArray(userObjBid.params.user[param])) { + let segs = []; + userObjBid.params.user[param].forEach(val => { + if (utils.isNumber(val)) { + segs.push({'id': val}); + } else if (utils.isPlainObject(val)) { + segs.push(val); + } + }); + userObj[uparam] = segs; + } else if (param !== 'segments') { + userObj[uparam] = userObjBid.params.user[param]; + } }); } diff --git a/test/spec/modules/appnexusBidAdapter_spec.js b/test/spec/modules/appnexusBidAdapter_spec.js index 426259639e8..9b12d892440 100644 --- a/test/spec/modules/appnexusBidAdapter_spec.js +++ b/test/spec/modules/appnexusBidAdapter_spec.js @@ -265,6 +265,7 @@ describe('AppNexusAdapter', function () { placementId: '10433394', user: { externalUid: '123', + segments: [123, { id: 987, value: 876 }], foobar: 'invalid' } } @@ -277,6 +278,7 @@ describe('AppNexusAdapter', function () { expect(payload.user).to.exist; expect(payload.user).to.deep.equal({ external_uid: '123', + segments: [{id: 123}, {id: 987, value: 876}] }); }); From c8353d39bc769e4fe3ee3b9a7dd3467dbd7bdf2c Mon Sep 17 00:00:00 2001 From: Hiroaki Kubota Date: Fri, 11 Dec 2020 08:56:10 +0900 Subject: [PATCH 100/103] Change craftBidAdapter request URL (#6096) --- modules/craftBidAdapter.js | 5 +++-- test/spec/modules/craftBidAdapter_spec.js | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/craftBidAdapter.js b/modules/craftBidAdapter.js index 3838f5dee59..0124f96a107 100644 --- a/modules/craftBidAdapter.js +++ b/modules/craftBidAdapter.js @@ -7,7 +7,7 @@ import includes from 'core-js-pure/features/array/includes.js'; import { getStorageManager } from '../src/storageManager.js'; const BIDDER_CODE = 'craft'; -const URL = 'https://gacraft.jp/prebid-v3'; +const URL_BASE = 'https://gacraft.jp/prebid-v3'; const TTL = 360; const storage = getStorageManager(); @@ -143,10 +143,11 @@ function formatRequest(payload, bidderRequest) { withCredentials: false }; } + const payloadString = JSON.stringify(payload); return { method: 'POST', - url: URL, + url: `${URL_BASE}/${payload.tags[0].sitekey}`, data: payloadString, bidderRequest, options diff --git a/test/spec/modules/craftBidAdapter_spec.js b/test/spec/modules/craftBidAdapter_spec.js index ef7dd7c3232..3f4bc977016 100644 --- a/test/spec/modules/craftBidAdapter_spec.js +++ b/test/spec/modules/craftBidAdapter_spec.js @@ -81,7 +81,7 @@ describe('craftAdapter', function () { it('sends bid request to ENDPOINT via POST', function () { let request = spec.buildRequests(bidRequests, bidderRequest); expect(request.method).to.equal('POST'); - expect(request.url).to.equal('https://gacraft.jp/prebid-v3'); + expect(request.url).to.equal('https://gacraft.jp/prebid-v3/craft-prebid-example'); let data = JSON.parse(request.data); expect(data.tags).to.deep.equals([{ sitekey: 'craft-prebid-example', From 0a5f9db693d54278e8198fb00be018089d892db4 Mon Sep 17 00:00:00 2001 From: Oleg Naydenov Date: Fri, 11 Dec 2020 20:59:20 +0200 Subject: [PATCH 101/103] Add Kubient bid adapter, Remove alias from Fidelity bid adapter. (#6084) * Add New Kubient Bid Adapter * Add New Kubient Bid Adapter * Fidelity Bid Adapter Update. Less 'Kubient' Alias * New Kubient Bid Adapter. Errors fix. --- modules/fidelityBidAdapter.js | 1 - modules/kubientBidAdapter.js | 111 +++++++++ modules/kubientBidAdapter.md | 26 ++ test/spec/modules/kubientBidAdapter_spec.js | 259 ++++++++++++++++++++ 4 files changed, 396 insertions(+), 1 deletion(-) create mode 100644 modules/kubientBidAdapter.js create mode 100644 modules/kubientBidAdapter.md create mode 100644 test/spec/modules/kubientBidAdapter_spec.js diff --git a/modules/fidelityBidAdapter.js b/modules/fidelityBidAdapter.js index baf5384fbfe..fac273721ff 100644 --- a/modules/fidelityBidAdapter.js +++ b/modules/fidelityBidAdapter.js @@ -6,7 +6,6 @@ const BIDDER_SERVER = 'x.fidelity-media.com'; const FIDELITY_VENDOR_ID = 408; export const spec = { code: BIDDER_CODE, - aliases: ['kubient'], gvlid: 408, isBidRequestValid: function isBidRequestValid(bid) { return !!(bid && bid.params && bid.params.zoneid); diff --git a/modules/kubientBidAdapter.js b/modules/kubientBidAdapter.js new file mode 100644 index 00000000000..8f6ea53ecce --- /dev/null +++ b/modules/kubientBidAdapter.js @@ -0,0 +1,111 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; +import * as utils from '../src/utils.js'; + +const BIDDER_CODE = 'kubient'; +const END_POINT = 'https://kssp.kbntx.ch/pbjs'; +const VERSION = '1.0'; +const VENDOR_ID = 794; +export const spec = { + code: BIDDER_CODE, + gvlid: VENDOR_ID, + supportedMediaTypes: [BANNER], + isBidRequestValid: function (bid) { + return !!(bid && bid.params); + }, + buildRequests: function (validBidRequests, bidderRequest) { + if (!validBidRequests || !bidderRequest) { + return; + } + const result = validBidRequests.map(function (bid) { + let data = { + v: VERSION, + requestId: bid.bidderRequestId, + adSlots: [{ + bidId: bid.bidId, + zoneId: bid.params.zoneid || '', + floor: bid.params.floor || 0.0, + sizes: bid.sizes || [], + schain: bid.schain || {}, + mediaTypes: bid.mediaTypes + }], + referer: (bidderRequest.refererInfo && bidderRequest.refererInfo.referer) ? bidderRequest.refererInfo.referer : null, + tmax: bidderRequest.timeout, + gdpr: (bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) ? 1 : 0, + consent: (bidderRequest.gdprConsent && bidderRequest.gdprConsent.consentString) ? bidderRequest.gdprConsent.consentString : null, + consentGiven: kubientGetConsentGiven(bidderRequest.gdprConsent), + uspConsent: bidderRequest.uspConsent + }; + return { + method: 'POST', + url: END_POINT, + data: JSON.stringify(data) + }; + }); + return result; + }, + interpretResponse: function interpretResponse(serverResponse, request) { + if (!serverResponse || !serverResponse.body || !serverResponse.body.seatbid) { + return []; + } + let bidResponses = []; + serverResponse.body.seatbid.forEach(seatbid => { + let bids = seatbid.bid || []; + bids.forEach(bid => { + bidResponses.push({ + requestId: bid.bidId, + cpm: bid.price, + currency: bid.cur, + width: bid.w, + height: bid.h, + creativeId: bid.creativeId, + netRevenue: bid.netRevenue, + ttl: bid.ttl, + ad: bid.adm + }); + }); + }); + return bidResponses; + }, + getUserSyncs: function (syncOptions, serverResponses, gdprConsent, uspConsent) { + const syncs = []; + let gdprParams = ''; + if (gdprConsent && typeof gdprConsent.consentString === 'string') { + gdprParams = `?consent_str=${gdprConsent.consentString}`; + if (typeof gdprConsent.gdprApplies === 'boolean') { + gdprParams = gdprParams + `&gdpr=${Number(gdprConsent.gdprApplies)}`; + } + gdprParams = gdprParams + `&consent_given=` + kubientGetConsentGiven(gdprConsent); + } + if (syncOptions.iframeEnabled) { + syncs.push({ + type: 'iframe', + url: 'https://kdmp.kbntx.ch/init.html' + gdprParams + }); + } + if (syncOptions.pixelEnabled) { + syncs.push({ + type: 'image', + url: 'https://kdmp.kbntx.ch/init.png' + gdprParams + }); + } + return syncs; + } +}; + +function kubientGetConsentGiven(gdprConsent) { + let consentGiven = 0; + if (typeof gdprConsent !== 'undefined') { + let apiVersion = utils.deepAccess(gdprConsent, `apiVersion`); + switch (apiVersion) { + case 1: + consentGiven = utils.deepAccess(gdprConsent, `vendorData.vendorConsents.${VENDOR_ID}`) ? 1 : 0; + break; + case 2: + consentGiven = utils.deepAccess(gdprConsent, `vendorData.vendor.consents.${VENDOR_ID}`) ? 1 : 0; + break; + } + } + return consentGiven; +} +registerBidder(spec); diff --git a/modules/kubientBidAdapter.md b/modules/kubientBidAdapter.md new file mode 100644 index 00000000000..9f3e1d5f52e --- /dev/null +++ b/modules/kubientBidAdapter.md @@ -0,0 +1,26 @@ +# Overview +​ +**Module Name**: Kubient Bidder Adapter +**Module Type**: Bidder Adapter +**Maintainer**: artem.aleksashkin@kubient.com +​ +# Description +​ +Connects to Kubient KSSP demand source to fetch bids. +​ +# Test Parameters +``` + var adUnits = [{ + code: 'banner-ad-div', + mediaTypes: { + banner: { + sizes: [[300, 250],[728, 90]], + } + }, + bids: [{ + "bidder": "kubient", + "params": { + "zoneid": "5fbb948f1e22b", + } + }] + }]; diff --git a/test/spec/modules/kubientBidAdapter_spec.js b/test/spec/modules/kubientBidAdapter_spec.js new file mode 100644 index 00000000000..1df4370b2ba --- /dev/null +++ b/test/spec/modules/kubientBidAdapter_spec.js @@ -0,0 +1,259 @@ +import { expect, assert } from 'chai'; +import { spec } from 'modules/kubientBidAdapter.js'; + +describe('KubientAdapter', function () { + let bid = { + bidId: '2dd581a2b6281d', + bidder: 'kubient', + bidderRequestId: '145e1d6a7837c9', + params: { + zoneid: '5678', + floor: 0.05, + }, + auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d2', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + transactionId: '3bb2f6da-87a6-4029-aeb0-bfe951372e62', + schain: { + ver: '1.0', + complete: 1, + nodes: [ + { + asi: 'example.com', + sid: '0', + hp: 1, + rid: 'bidrequestid', + domain: 'example.com' + } + ] + } + }; + let consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; + let uspConsentData = '1YCC'; + let bidderRequest = { + bidderCode: 'kubient', + auctionId: 'fffffff-ffff-ffff-ffff-ffffffffffff', + bidderRequestId: 'ffffffffffffff', + start: 1472239426002, + auctionStart: 1472239426000, + timeout: 5000, + refererInfo: { + referer: 'http://www.example.com', + reachedTop: true, + }, + gdprConsent: { + consentString: consentString, + gdprApplies: true + }, + uspConsent: uspConsentData, + bids: [bid] + }; + describe('buildRequests', function () { + let serverRequests = spec.buildRequests([bid], bidderRequest); + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequests).to.be.an('array'); + }); + for (let i = 0; i < serverRequests.length; i++) { + let serverRequest = serverRequests[i]; + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequest.method).to.be.a('string'); + expect(serverRequest.url).to.be.a('string'); + expect(serverRequest.data).to.be.a('string'); + }); + it('Returns POST method', function () { + expect(serverRequest.method).to.equal('POST'); + }); + it('Returns valid URL', function () { + expect(serverRequest.url).to.equal('https://kssp.kbntx.ch/pbjs'); + }); + it('Returns valid data if array of bids is valid', function () { + let data = JSON.parse(serverRequest.data); + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('v', 'requestId', 'adSlots', 'gdpr', 'referer', 'tmax', 'consent', 'consentGiven', 'uspConsent'); + expect(data.v).to.exist.and.to.be.a('string'); + expect(data.requestId).to.exist.and.to.be.a('string'); + expect(data.referer).to.be.a('string'); + expect(data.tmax).to.exist.and.to.be.a('number'); + expect(data.gdpr).to.exist.and.to.be.within(0, 1); + expect(data.consent).to.equal(consentString); + expect(data.uspConsent).to.exist.and.to.equal(uspConsentData); + for (let j = 0; j < data['adSlots'].length; j++) { + let adSlot = data['adSlots'][i]; + expect(adSlot).to.have.all.keys('bidId', 'zoneId', 'floor', 'sizes', 'schain', 'mediaTypes'); + expect(adSlot.bidId).to.be.a('string'); + expect(adSlot.zoneId).to.be.a('string'); + expect(adSlot.floor).to.be.a('number'); + expect(adSlot.sizes).to.be.an('array'); + expect(adSlot.schain).to.be.an('object'); + expect(adSlot.mediaTypes).to.be.an('object'); + } + }); + } + }); + + describe('isBidRequestValid', function () { + it('Should return true when required params are found', function () { + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + it('Should return false when required params are not found', function () { + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + it('Should return false when params are not found', function () { + delete bid.params; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + }); + + describe('interpretResponse', function () { + it('Should interpret response', function () { + const serverResponse = { + body: + { + seatbid: [ + { + bid: [ + { + bidId: '000', + price: 1.5, + adm: '
test
', + creativeId: 'creativeId', + w: 300, + h: 250, + cur: 'USD', + netRevenue: false, + ttl: 360 + } + ] + } + ] + } + }; + let bannerResponses = spec.interpretResponse(serverResponse); + expect(bannerResponses).to.be.an('array').that.is.not.empty; + let dataItem = bannerResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'ad', 'creativeId', 'width', 'height', 'currency', 'netRevenue', 'ttl'); + expect(dataItem.requestId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].bidId); + expect(dataItem.cpm).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].price); + expect(dataItem.ad).to.exist.and.to.be.a('string').and.to.have.string(serverResponse.body.seatbid[0].bid[0].adm); + expect(dataItem.creativeId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].creativeId); + expect(dataItem.width).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].w); + expect(dataItem.height).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].h); + expect(dataItem.currency).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].cur); + expect(dataItem.netRevenue).to.exist.and.to.be.a('boolean').and.to.equal(serverResponse.body.seatbid[0].bid[0].netRevenue); + expect(dataItem.ttl).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].ttl); + }); + + it('Should return no ad when not given a server response', function () { + const ads = spec.interpretResponse(null); + expect(ads).to.be.an('array').and.to.have.length(0); + }); + }); + + describe('getUserSyncs', function () { + it('should register the sync iframe without gdpr', function () { + let syncOptions = { + iframeEnabled: true + }; + let serverResponses = null; + let gdprConsent = { + consentString: consentString + }; + let uspConsent = null; + let syncs = spec.getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent); + expect(syncs).to.be.an('array').and.to.have.length(1); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.equal('https://kdmp.kbntx.ch/init.html?consent_str=' + consentString + '&consent_given=0'); + }); + it('should register the sync iframe with gdpr', function () { + let syncOptions = { + iframeEnabled: true + }; + let serverResponses = null; + let gdprConsent = { + gdprApplies: true, + consentString: consentString + }; + let uspConsent = null; + let syncs = spec.getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent); + expect(syncs).to.be.an('array').and.to.have.length(1); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.equal('https://kdmp.kbntx.ch/init.html?consent_str=' + consentString + '&gdpr=1&consent_given=0'); + }); + it('should register the sync iframe with gdpr vendor', function () { + let syncOptions = { + iframeEnabled: true + }; + let serverResponses = null; + let gdprConsent = { + gdprApplies: true, + consentString: consentString, + apiVersion: 1, + vendorData: { + vendorConsents: { + 794: 1 + } + } + }; + let uspConsent = null; + let syncs = spec.getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent); + expect(syncs).to.be.an('array').and.to.have.length(1); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.equal('https://kdmp.kbntx.ch/init.html?consent_str=' + consentString + '&gdpr=1&consent_given=1'); + }); + it('should register the sync image without gdpr', function () { + let syncOptions = { + pixelEnabled: true + }; + let serverResponses = null; + let gdprConsent = { + consentString: consentString + }; + let uspConsent = null; + let syncs = spec.getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent); + expect(syncs).to.be.an('array').and.to.have.length(1); + expect(syncs[0].type).to.equal('image'); + expect(syncs[0].url).to.equal('https://kdmp.kbntx.ch/init.png?consent_str=' + consentString + '&consent_given=0'); + }); + it('should register the sync image with gdpr', function () { + let syncOptions = { + pixelEnabled: true + }; + let serverResponses = null; + let gdprConsent = { + gdprApplies: true, + consentString: consentString + }; + let uspConsent = null; + let syncs = spec.getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent); + expect(syncs).to.be.an('array').and.to.have.length(1); + expect(syncs[0].type).to.equal('image'); + expect(syncs[0].url).to.equal('https://kdmp.kbntx.ch/init.png?consent_str=' + consentString + '&gdpr=1&consent_given=0'); + }); + it('should register the sync image with gdpr vendor', function () { + let syncOptions = { + pixelEnabled: true + }; + let serverResponses = null; + let gdprConsent = { + gdprApplies: true, + consentString: consentString, + apiVersion: 2, + vendorData: { + vendor: { + consents: { + 794: 1 + } + } + } + }; + let uspConsent = null; + let syncs = spec.getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent); + expect(syncs).to.be.an('array').and.to.have.length(1); + expect(syncs[0].type).to.equal('image'); + expect(syncs[0].url).to.equal('https://kdmp.kbntx.ch/init.png?consent_str=' + consentString + '&gdpr=1&consent_given=1'); + }); + }) +}); From 9f38423d98a9572163541957a50c70f1dedae037 Mon Sep 17 00:00:00 2001 From: Mathieu Pheulpin Date: Fri, 11 Dec 2020 13:30:24 -0800 Subject: [PATCH 102/103] [Sharethrough] Add Support for badv/bcat and Identity Link User ID (#6100) * Add support for badv and bcat [#175358412] Co-authored-by: Michael Duran * Add support for Identity Link [#175688307] Co-authored-by: Michael Duran Co-authored-by: Michael Duran --- modules/sharethroughBidAdapter.js | 16 +++++- modules/sharethroughBidAdapter.md | 8 ++- .../modules/sharethroughBidAdapter_spec.js | 54 ++++++++++++++++--- 3 files changed, 67 insertions(+), 11 deletions(-) diff --git a/modules/sharethroughBidAdapter.js b/modules/sharethroughBidAdapter.js index 7df161db713..89484b1c68b 100644 --- a/modules/sharethroughBidAdapter.js +++ b/modules/sharethroughBidAdapter.js @@ -1,7 +1,7 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import * as utils from '../src/utils.js'; -const VERSION = '3.2.1'; +const VERSION = '3.3.0'; const BIDDER_CODE = 'sharethrough'; const STR_ENDPOINT = 'https://btlr.sharethrough.com/WYu2BXv1/v1'; const DEFAULT_SIZE = [1, 1]; @@ -56,6 +56,10 @@ export const sharethroughAdapterSpec = { query.pubcid = bidRequest.crumbs.pubcid; } + if (bidRequest.userId && bidRequest.userId.idl_env) { + query.idluid = bidRequest.userId.idl_env; + } + if (bidRequest.schain) { query.schain = JSON.stringify(bidRequest.schain); } @@ -64,6 +68,14 @@ export const sharethroughAdapterSpec = { query.bidfloor = parseFloat(bidRequest.bidfloor); } + if (bidRequest.params.badv) { + query.badv = bidRequest.params.badv; + } + + if (bidRequest.params.bcat) { + query.bcat = bidRequest.params.bcat; + } + // Data that does not need to go to the server, // but we need as part of interpretResponse() const strData = { @@ -73,7 +85,7 @@ export const sharethroughAdapterSpec = { }; return { - method: 'GET', + method: 'POST', url: STR_ENDPOINT, data: query, strData: strData diff --git a/modules/sharethroughBidAdapter.md b/modules/sharethroughBidAdapter.md index 2290e370cae..396b8164577 100644 --- a/modules/sharethroughBidAdapter.md +++ b/modules/sharethroughBidAdapter.md @@ -29,7 +29,13 @@ Module that connects to Sharethrough's demand sources // OPTIONAL - If iframeSize is provided, we'll use this size for the iframe // otherwise we'll grab the largest size from the sizes array // This is ignored if iframe: false - iframeSize: [250, 250] + iframeSize: [250, 250], + + // OPTIONAL - Blocked Advertiser Domains + badv: ['domain1.com', 'domain2.com'], + + // OPTIONAL - Blocked Categories (IAB codes) + bcat: ['IAB1-1', 'IAB1-2'], } } ] diff --git a/test/spec/modules/sharethroughBidAdapter_spec.js b/test/spec/modules/sharethroughBidAdapter_spec.js index d45d1e977e6..cd9071a6098 100644 --- a/test/spec/modules/sharethroughBidAdapter_spec.js +++ b/test/spec/modules/sharethroughBidAdapter_spec.js @@ -14,7 +14,8 @@ const bidRequests = [ }, userId: { tdid: 'fake-tdid', - pubcid: 'fake-pubcid' + pubcid: 'fake-pubcid', + idl_env: 'fake-identity-link' }, crumbs: { pubcid: 'fake-pubcid-in-crumbs-obj' @@ -40,12 +41,32 @@ const bidRequests = [ iframe: true, iframeSize: [500, 500] } - } + }, + { + bidder: 'sharethrough', + bidId: 'bidId4', + sizes: [[700, 400]], + placementCode: 'bar', + params: { + pkey: 'dddd4444', + badv: ['domain1.com', 'domain2.com'] + } + }, + { + bidder: 'sharethrough', + bidId: 'bidId5', + sizes: [[700, 400]], + placementCode: 'bar', + params: { + pkey: 'eeee5555', + bcat: ['IAB1-1', 'IAB1-2'] + } + }, ]; const prebidRequests = [ { - method: 'GET', + method: 'POST', url: 'https://btlr.sharethrough.com/WYu2BXv1/v1', data: { bidId: 'bidId', @@ -57,7 +78,7 @@ const prebidRequests = [ } }, { - method: 'GET', + method: 'POST', url: 'https://btlr.sharethrough.com/WYu2BXv1/v1', data: { bidId: 'bidId', @@ -69,7 +90,7 @@ const prebidRequests = [ } }, { - method: 'GET', + method: 'POST', url: 'https://btlr.sharethrough.com/WYu2BXv1/v1', data: { bidId: 'bidId', @@ -82,7 +103,7 @@ const prebidRequests = [ } }, { - method: 'GET', + method: 'POST', url: 'https://btlr.sharethrough.com/WYu2BXv1/v1', data: { bidId: 'bidId', @@ -94,7 +115,7 @@ const prebidRequests = [ } }, { - method: 'GET', + method: 'POST', url: 'https://btlr.sharethrough.com/WYu2BXv1/v1', data: { bidId: 'bidId', @@ -225,7 +246,7 @@ describe('sharethrough adapter spec', function() { expect(builtBidRequests[0].url).to.eq('https://btlr.sharethrough.com/WYu2BXv1/v1'); expect(builtBidRequests[1].url).to.eq('https://btlr.sharethrough.com/WYu2BXv1/v1'); - expect(builtBidRequests[0].method).to.eq('GET'); + expect(builtBidRequests[0].method).to.eq('POST'); }); it('should set the instant_play_capable parameter correctly based on browser userAgent string', function() { @@ -315,6 +336,11 @@ describe('sharethrough adapter spec', function() { expect(bidRequest.data.pubcid).to.eq('fake-pubcid'); }); + it('should add the idluid parameter if a bid request contains a value for Identity Link from Live Ramp', function() { + const bidRequest = spec.buildRequests(bidRequests)[0]; + expect(bidRequest.data.idluid).to.eq('fake-identity-link'); + }); + it('should add Sharethrough specific parameters', function() { const builtBidRequests = spec.buildRequests(bidRequests); expect(builtBidRequests[0]).to.deep.include({ @@ -346,6 +372,18 @@ describe('sharethrough adapter spec', function() { expect(builtBidRequest.data.schain).to.eq(JSON.stringify(bidRequest.schain)); }); + it('should add badv if provided', () => { + const builtBidRequest = spec.buildRequests([bidRequests[3]])[0]; + + expect(builtBidRequest.data.badv).to.have.members(['domain1.com', 'domain2.com']) + }); + + it('should add bcat if provided', () => { + const builtBidRequest = spec.buildRequests([bidRequests[4]])[0]; + + expect(builtBidRequest.data.bcat).to.have.members(['IAB1-1', 'IAB1-2']) + }); + it('should not add a supply chain parameter if schain is missing', function() { const bidRequest = spec.buildRequests(bidRequests)[0]; expect(bidRequest.data).to.not.include.any.keys('schain'); From 25270510561bf2b8d3f303d25b0a6c80277fd03c Mon Sep 17 00:00:00 2001 From: Egor Gordeev <48566506+egsgordeev@users.noreply.github.com> Date: Mon, 14 Dec 2020 10:51:48 +0400 Subject: [PATCH 103/103] Sovrn: Pass the imp.ext.deals field (#6098) * EX-2549 Pass segments parameter as imp.ext.dealids array * EX-2549 Address Jon's feedback * EX-2549 Reworked the solution * EX-2549 Blackbird compatibility * EX-2549 Address Jon's comments * EX-2549 Addressed upstream PR comments --- modules/sovrnBidAdapter.js | 14 ++++++++++-- test/spec/modules/sovrnBidAdapter_spec.js | 26 +++++++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/modules/sovrnBidAdapter.js b/modules/sovrnBidAdapter.js index 62f5e85779e..8f8158fd0c9 100644 --- a/modules/sovrnBidAdapter.js +++ b/modules/sovrnBidAdapter.js @@ -53,7 +53,7 @@ export const spec = { bidSizes = ((utils.isArray(bidSizes) && utils.isArray(bidSizes[0])) ? bidSizes : [bidSizes]) bidSizes = bidSizes.filter(size => utils.isArray(size)) const processedSizes = bidSizes.map(size => ({w: parseInt(size[0], 10), h: parseInt(size[1], 10)})) - sovrnImps.push({ + const imp = { adunitcode: bid.adUnitCode, id: bid.bidId, banner: { @@ -63,7 +63,17 @@ export const spec = { }, tagid: String(utils.getBidIdParameter('tagid', bid.params)), bidfloor: utils.getBidIdParameter('bidfloor', bid.params) - }); + } + + const segmentsString = utils.getBidIdParameter('segments', bid.params) + + if (segmentsString) { + imp.ext = { + deals: segmentsString.split(',').map(deal => deal.trim()) + } + } + + sovrnImps.push(imp); }); const page = bidderRequest.refererInfo.referer diff --git a/test/spec/modules/sovrnBidAdapter_spec.js b/test/spec/modules/sovrnBidAdapter_spec.js index 983ade4dd14..769be73a272 100644 --- a/test/spec/modules/sovrnBidAdapter_spec.js +++ b/test/spec/modules/sovrnBidAdapter_spec.js @@ -276,6 +276,32 @@ describe('sovrnBidAdapter', function() { expect(data.user.ext.tpid[0].uid).to.equal('A_CRITEO_ID') expect(data.user.ext.prebid_criteoid).to.equal('A_CRITEO_ID') }); + + it('should ignore empty segments', function() { + const payload = JSON.parse(request.data) + expect(payload.imp[0].ext).to.be.undefined + }) + + it('should pass the segments param value as trimmed deal ids array', function() { + const segmentsRequests = [{ + 'bidder': 'sovrn', + 'params': { + 'segments': ' test1,test2 ' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [300, 250], + [300, 600] + ], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475' + }]; + const request = spec.buildRequests(segmentsRequests, bidderRequest) + const payload = JSON.parse(request.data) + expect(payload.imp[0].ext.deals[0]).to.equal('test1') + expect(payload.imp[0].ext.deals[1]).to.equal('test2') + }) }); describe('interpretResponse', function () {