diff --git a/.gitignore b/.gitignore index 1e4d5248000..c0a1a2f8ba4 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ build # Test Files test/app gpt.html +gpt-each-bidder3.html # Dev File diff --git a/src/adaptermanager.js b/src/adaptermanager.js index 1b9e4d12758..0243ec11c00 100644 --- a/src/adaptermanager.js +++ b/src/adaptermanager.js @@ -10,8 +10,8 @@ import { BaseAdapter } from './adapters/baseAdapter'; var _bidderRegistry = {}; exports.bidderRegistry = _bidderRegistry; -function getBids({ bidderCode, requestId, bidderRequestId }) { - return pbjs.adUnits.map(adUnit => { +function getBids({ bidderCode, requestId, bidderRequestId, adUnits }) { + return adUnits.map(adUnit => { return adUnit.bids.filter(bid => bid.bidder === bidderCode) .map(bid => Object.assign(bid, { placementCode: adUnit.code, @@ -23,10 +23,10 @@ function getBids({ bidderCode, requestId, bidderRequestId }) { }).reduce(flatten, []); } -exports.callBids = () => { +exports.callBids = ({ adUnits }) => { const requestId = utils.getUniqueIdentifierStr(); - getBidderCodes().forEach(bidderCode => { + getBidderCodes(adUnits).forEach(bidderCode => { const adapter = _bidderRegistry[bidderCode]; if (adapter) { const bidderRequestId = utils.getUniqueIdentifierStr(); @@ -34,7 +34,7 @@ exports.callBids = () => { bidderCode, requestId, bidderRequestId, - bids: getBids({ bidderCode, requestId, bidderRequestId }), + bids: getBids({ bidderCode, requestId, bidderRequestId, adUnits }), start: new Date().getTime() }; utils.logMessage(`CALLING BIDDER ======= ${bidderCode}`); diff --git a/src/bidmanager.js b/src/bidmanager.js index 7e053b83f60..e0f9adbe35e 100644 --- a/src/bidmanager.js +++ b/src/bidmanager.js @@ -22,7 +22,7 @@ const _hgPriceCap = 20.00; */ exports.getTimedOutBidders = function () { return pbjs._bidsRequested - .map(getBidderCodes) + .map(getBidderCode) .filter(uniques) .filter(bidder => pbjs._bidsReceived .map(getBidders) @@ -32,7 +32,7 @@ exports.getTimedOutBidders = function () { function timestamp() { return new Date().getTime(); } -function getBidderCodes(bidSet) { +function getBidderCode(bidSet) { return bidSet.bidderCode; } @@ -236,6 +236,8 @@ exports.executeCallback = function () { processCallbacks([externalOneTimeCallback]); externalOneTimeCallback = null; } + + pbjs.clearAuction(); }; function triggerAdUnitCallbacks(adUnitCode) { @@ -249,11 +251,36 @@ function processCallbacks(callbackQueue) { if (utils.isArray(callbackQueue)) { for (i = 0; i < callbackQueue.length; i++) { var func = callbackQueue[i]; - func.call(pbjs, pbjs._bidsReceived); + func.call(pbjs, pbjs._bidsReceived.reduce(groupByPlacement, {})); } } } +/** + * groupByPlacement is a reduce function that converts an array of Bid objects + * to an object with placement codes as keys, with each key representing an object + * with an array of `Bid` objects for that placement + * @param prev previous value as accumulator object + * @param item current array item + * @param idx current index + * @param arr the array being reduced + * @returns {*} as { [adUnitCode]: { bids: [Bid, Bid, Bid] } } + */ +function groupByPlacement(prev, item, idx, arr) { + // this uses a standard "array to map" operation that could be abstracted further + if (item.adUnitCode in Object.keys(prev)) { + // if the adUnitCode key is present in the accumulator object, continue + return prev; + } else { + // otherwise add the adUnitCode key to the accumulator object and set to an object with an + // array of Bids for that adUnitCode + prev[item.adUnitCode] = { + bids: arr.filter(bid => bid.adUnitCode === item.adUnitCode) + }; + return prev; + } +} + /** * Add a one time callback, that is discarded after it is called * @param {Function} callback [description] diff --git a/src/prebid.js b/src/prebid.js index 60bd3804c99..311ead9e640 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -26,6 +26,8 @@ var BID_TIMEOUT = CONSTANTS.EVENTS.BID_TIMEOUT; var pb_bidsTimedOut = false; var pb_sendAllBids = false; +var auctionRunning = false; +var presetTargeting = []; var eventValidators = { bidWon: checkDefinedPlacement @@ -35,6 +37,7 @@ var eventValidators = { pbjs._bidsRequested = []; pbjs._bidsReceived = []; +pbjs._adsReceived = []; //default timeout for all bids pbjs.bidderTimeout = pbjs.bidderTimeout || 2000; @@ -101,11 +104,35 @@ function checkDefinedPlacement(id) { return true; } +function resetPresetTargeting() { + if (isGptPubadsDefined()) { + window.googletag.pubads().getSlots().forEach(slot => { + slot.clearTargeting(); + }); + + setTargeting(presetTargeting); + } +} + +function setTargeting(targetingConfig) { + window.googletag.pubads().getSlots().forEach(slot => { + targetingConfig.filter(targeting => Object.keys(targeting)[0] === slot.getAdUnitPath() || + Object.keys(targeting)[0] === slot.getSlotElementId()) + .forEach(targeting => targeting[Object.keys(targeting)[0]] + .forEach(key => { + key[Object.keys(key)[0]] + .map((value) => { + utils.logMessage(`Attempting to set key value for slot: ${slot.getSlotElementId()} key: ${Object.keys(key)[0]} value: ${value}`); + return value; + }) + .forEach(value => slot.setTargeting(Object.keys(key)[0], value)); + })); + }); +} function getWinningBidTargeting() { - let presets; if (isGptPubadsDefined()) { - presets = (function getPresetTargeting() { + presetTargeting = (function getPresetTargeting() { return window.googletag.pubads().getSlots().map(slot => { return { [slot.getAdUnitPath()]: slot.getTargetingKeys().map(key => { @@ -125,7 +152,7 @@ function getWinningBidTargeting() { adUnitCode: adUnitCode, cpm: 0, adserverTargeting: {}, - timeToRespond : 0 + timeToRespond: 0 })); winners = winners.map(winner => { @@ -137,8 +164,8 @@ function getWinningBidTargeting() { }; }); - if (presets) { - winners.concat(presets); + if (presetTargeting) { + winners.concat(presetTargeting); } return winners; @@ -189,12 +216,10 @@ pbjs.getAdserverTargetingForAdUnitCodeStr = function (adunitCode) { }; /** - * This function returns the query string targeting parameters available at this moment for a given ad unit. Note that some bidder's response may not have been received if you call this function too quickly after the requests are sent. - * @param {string} [adunitCode] adUnitCode to get the bid responses for - * @alias module:pbjs.getAdserverTargetingForAdUnitCode - * @return {object} returnObj return bids +* This function returns the query string targeting parameters available at this moment for a given ad unit. Note that some bidder's response may not have been received if you call this function too quickly after the requests are sent. + * @param adUnitCode {string} adUnitCode to get the bid responses for + * @returns {object} returnObj return bids */ - pbjs.getAdserverTargetingForAdUnitCode = function (adUnitCode) { utils.logInfo('Invoking pbjs.getAdserverTargetingForAdUnitCode', arguments); @@ -287,20 +312,7 @@ pbjs.setTargetingForGPTAsync = function () { return; } - window.googletag.pubads().getSlots().forEach(slot => { - getAllTargeting() - .filter(targeting => Object.keys(targeting)[0] === slot.getAdUnitPath() || - Object.keys(targeting)[0] === slot.getSlotElementId()) - .forEach(targeting => targeting[Object.keys(targeting)[0]] - .forEach(key => { - key[Object.keys(key)[0]] - .map((value, index, array) => { - utils.logMessage(`Attempting to set key value for slot: ${slot.getSlotElementId()} key: ${Object.keys(key)[0]} value: ${value}`); - return value; - }) - .forEach(value => slot.setTargeting(Object.keys(key)[0], value)); - })); - }); + setTargeting(getAllTargeting()); }; /** @@ -387,21 +399,47 @@ pbjs.removeAdUnit = function (adUnitCode) { } }; +pbjs.clearAuction = function() { + auctionRunning = false; + utils.logMessage('Prebid auction cleared'); +}; + /** * * @param bidsBackHandler * @param timeout + * @param adUnits + * @param adUnitCodes */ -pbjs.requestBids = function ({ bidsBackHandler, timeout }) { +pbjs.requestBids = function ({ bidsBackHandler, timeout, adUnits, adUnitCodes }) { + if (auctionRunning) { + utils.logError('Prebid Error: `pbjs.requestBids` was called while a previous auction was' + + ' still running. Resubmit this request.'); + return; + } else { + auctionRunning = true; + pbjs._bidsRequested = []; + pbjs._bidsReceived = []; + resetPresetTargeting(); + } + const cbTimeout = timeout || pbjs.bidderTimeout; + // use adUnits provided or from pbjs global + adUnits = adUnits || pbjs.adUnits; + + // if specific adUnitCodes filter adUnits for those codes + if (adUnitCodes && adUnitCodes.length) { + adUnits = adUnits.filter(adUnit => adUnitCodes.includes(adUnit.code)); + } + if (typeof bidsBackHandler === objectType_function) { bidmanager.addOneTimeCallback(bidsBackHandler); } utils.logInfo('Invoking pbjs.requestBids', arguments); - if (!pbjs.adUnits || pbjs.adUnits.length === 0) { + if (!adUnits || adUnits.length === 0) { utils.logMessage('No adUnits configured. No bids requested.'); return; } @@ -409,7 +447,7 @@ pbjs.requestBids = function ({ bidsBackHandler, timeout }) { //set timeout for all bids setTimeout(bidmanager.executeCallback, cbTimeout); - adaptermanager.callBids(); + adaptermanager.callBids({ adUnits, adUnitCodes }); }; /** diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index 83ca498731d..68ecc092200 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -19,6 +19,12 @@ pbjs = pbjs || {}; pbjs._bidsRequested = getBidRequests(); pbjs._bidsReceived = getBidResponses(); +function resetAuction() { + pbjs.clearAuction(); + pbjs._bidsRequested = getBidRequests(); + pbjs._bidsReceived = getBidResponses(); +} + var Slot = function Slot(elementId, pathId) { var slot = { getSlotElementId: function getSlotElementId() { @@ -38,6 +44,10 @@ var Slot = function Slot(elementId, pathId) { getTargetingKeys: function getTargetingKeys() { return ['testKey']; + }, + + clearTargeting: function clearTargeting() { + return googletag.pubads().getSlots(); } }; slot.spySetTargeting = sinon.spy(slot, 'setTargeting'); @@ -282,6 +292,7 @@ describe('Unit: Prebid Module', function () { assert.ok(spyAddOneTimeCallBack.calledWith(requestObj.bidsBackHandler), 'called bidmanager.addOneTimeCallback'); bidmanager.addOneTimeCallback.restore(); + resetAuction(); }); it('should log message when adUnits not configured', () => { @@ -294,6 +305,7 @@ describe('Unit: Prebid Module', function () { assert.ok(logMessageSpy.calledWith('No adUnits configured. No bids requested.'), 'expected message was logged'); utils.logMessage.restore(); pbjs.adUnits = adUnitsBackup; + resetAuction(); }); it('should execute callback after timeout', () => { @@ -314,6 +326,7 @@ describe('Unit: Prebid Module', function () { bidmanager.executeCallback.restore(); clock.restore(); + resetAuction(); }); it('should call callBids function on adaptermanager', () => { @@ -321,6 +334,7 @@ describe('Unit: Prebid Module', function () { pbjs.requestBids({}); assert.ok(spyCallBids.called, 'called adaptermanager.callBids'); adaptermanager.callBids.restore(); + resetAuction(); }); });