diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 1dc04a9c2b7..61eb327fd2c 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -26,6 +26,9 @@ Thank you for your pull request. Please make sure this PR is scoped to one chang } } ``` + +Be sure to test the integration with your adserver using the [Hello World](/integrationExamples/gpt/hello_world.html) sample page. + - contact email of the adapter’s maintainer - [ ] official adapter submission diff --git a/CHANGELOG b/CHANGELOG index c1a03f7ba63..22cced6a558 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,8 @@ +AOL Prebid 1.24.0 +---------------- +Updated to Prebid 0.25.0 + + AOL Prebid 1.23.0 ---------------- Added passing key values feature. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3252375ac68..5856835f785 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -31,6 +31,8 @@ Before a Pull Request will be considered for merge: - All new and existing tests must pass - Added or modified code must have greater than 80% coverage +If you are submitting an adapter, you can also use the [Hello World](integrationExamples/gpt/hello_world.html) example page to test integration with your server. + ### Test Guidelines When you are adding code to Prebid.js, or modifying code that isn't covered by an existing test, test the code according to these guidelines: diff --git a/adapters.json b/adapters.json index b6d1b41ad70..7d62a26c235 100644 --- a/adapters.json +++ b/adapters.json @@ -7,6 +7,7 @@ "adform", "adkernel", "admedia", + "adyoulike", "bidfluence", "vertamedia", "aol", @@ -14,6 +15,7 @@ "appnexusAst", "beachfront", "audienceNetwork", + "carambola", "conversant", "districtmDMX", "fidelity", @@ -66,8 +68,10 @@ "atomx", "tapsense", "trion", + "eplanning", "prebidServer", "adsupply", + "cox", { "aol": { "alias": "onemobile" @@ -153,5 +157,15 @@ "rhythmone": { "supportedMediaTypes": ["video"] } + }, + { + "admixer": { + "supportedMediaTypes": ["video"] + } + }, + { + "conversant": { + "supportedMediaTypes": ["video"] + } } ] diff --git a/gulpfile.js b/gulpfile.js index e2511fd0dad..47ab725b4aa 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -46,7 +46,7 @@ gulp.task('clean', function () { .pipe(clean()); }); -gulp.task('devpack', function () { +gulp.task('devpack', ['clean'], function () { webpackConfig.devtool = 'source-map'; const analyticsSources = helpers.getAnalyticsSources(analyticsDirectory); return gulp.src([].concat(analyticsSources, 'src/prebid.js')) @@ -201,12 +201,11 @@ gulp.task('watch', function () { 'src/**/*.js', 'test/spec/**/*.js', '!test/spec/loaders/**/*.js' - ], ['lint', 'webpack', 'devpack', 'test']); + ], ['clean', 'lint', 'webpack', 'devpack', 'test']); gulp.watch([ 'loaders/**/*.js', 'test/spec/loaders/**/*.js' ], ['lint', 'mocha']); - gulp.watch(['integrationExamples/gpt/*.html'], ['test']); connect.server({ https: argv.https, port: port, diff --git a/integrationExamples/gpt/.gitignore b/integrationExamples/gpt/.gitignore deleted file mode 100644 index fb64ad0cc6e..00000000000 --- a/integrationExamples/gpt/.gitignore +++ /dev/null @@ -1 +0,0 @@ -pbjs_adblade_example_gpt.html diff --git a/integrationExamples/gpt/hello_world.html b/integrationExamples/gpt/hello_world.html new file mode 100644 index 00000000000..0f5e24a301a --- /dev/null +++ b/integrationExamples/gpt/hello_world.html @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + +

Prebid.js Test

+
Div-1
+
+ +
+ + diff --git a/package.json b/package.json index c906d2459d8..4d3832e1fe8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "0.24.1", + "version": "0.25.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { diff --git a/src/Renderer.js b/src/Renderer.js index c4e926ca4d5..c81049fa1f6 100644 --- a/src/Renderer.js +++ b/src/Renderer.js @@ -2,19 +2,37 @@ import { loadScript } from 'src/adloader'; import * as utils from 'src/utils'; export function Renderer(options) { - const { url, config, id, callback } = options; + const { url, config, id, callback, loaded } = options; this.url = url; this.config = config; - this.callback = callback; this.handlers = {}; this.id = id; + // a renderer may push to the command queue to delay rendering until the + // render function is loaded by loadScript, at which point the the command + // queue will be processed + this.loaded = loaded; + this.cmd = []; + this.push = func => { + if (typeof func !== 'function') { + utils.logError('Commands given to Renderer.push must be wrapped in a function'); + return; + } + this.loaded ? func.call() : this.cmd.push(func); + }; + + // bidders may override this with the `callback` property given to `install` + this.callback = callback || (() => { + this.loaded = true; + this.process(); + }); + // we expect to load a renderer url once only so cache the request to load script - loadScript(url, callback, true); + loadScript(url, this.callback, true); } -Renderer.install = function({ url, config, id, callback }) { - return new Renderer({ url, config, id, callback }); +Renderer.install = function({ url, config, id, callback, loaded }) { + return new Renderer({ url, config, id, callback, loaded }); }; Renderer.prototype.getConfig = function() { @@ -36,3 +54,17 @@ Renderer.prototype.handleVideoEvent = function({ id, eventName }) { utils.logMessage(`Prebid Renderer event for id ${id} type ${eventName}`); }; + +/* + * Calls functions that were pushed to the command queue before the + * renderer was loaded by `loadScript` + */ +Renderer.prototype.process = function() { + while (this.cmd.length > 0) { + try { + this.cmd.shift().call(); + } catch (error) { + utils.logError('Error processing Renderer command: ', error); + } + } +}; diff --git a/src/adapters/adform.js b/src/adapters/adform.js index 660c1c585b0..31eceba0b0d 100644 --- a/src/adapters/adform.js +++ b/src/adapters/adform.js @@ -10,11 +10,11 @@ function AdformAdapter() { }; function _callBids(params) { - var bid, _value, _key, i, j, k, l; + var bid, _value, _key, i, j, k, l, reqParams; var bids = params.bids; var request = []; var callbackName = '_adf_' + utils.getUniqueIdentifierStr(); - var globalParams = [ [ 'adxDomain', 'adx.adform.net' ], [ 'url', null ], [ 'tid', null ], [ 'callback', '$$PREBID_GLOBAL$$.' + callbackName ] ]; + var globalParams = [ [ 'adxDomain', 'adx.adform.net' ], ['fd', 1], [ 'url', null ], [ 'tid', null ], [ 'callback', '$$PREBID_GLOBAL$$.' + callbackName ] ]; for (i = 0, l = bids.length; i < l; i++) { bid = bids[i]; @@ -28,7 +28,9 @@ function AdformAdapter() { } } - request.push(formRequestUrl(bid.params)); + reqParams = bid.params; + reqParams.transactionId = bid.transactionId; + request.push(formRequestUrl(reqParams)); } request.unshift('//' + globalParams[0][1] + '/adx/?rp=4'); @@ -76,6 +78,7 @@ function AdformAdapter() { bidObject.width = adItem.width; bidObject.height = adItem.height; bidObject.dealId = adItem.deal_id; + bidObject.transactionId = bid.transactionId; bidmanager.addBidResponse(bid.placementCode, bidObject); } else { bidObject = bidfactory.createBid(STATUSCODES.NO_BID, bid); diff --git a/src/adapters/admixer.js b/src/adapters/admixer.js index 944aed276ce..24cf81bf9e9 100644 --- a/src/adapters/admixer.js +++ b/src/adapters/admixer.js @@ -10,6 +10,7 @@ var utils = require('../utils.js'); */ var AdmixerAdapter = function AdmixerAdapter() { var invUrl = '//inv-nets.admixer.net/prebid.aspx'; + var invVastUrl = '//inv-nets.admixer.net/videoprebid.aspx'; function _callBids(data) { var bids = data.bids || []; @@ -21,7 +22,16 @@ var AdmixerAdapter = function AdmixerAdapter() { 'callback_uid': bid.placementCode }; if (params.zone) { - _requestBid(invUrl, params); + if (bid.mediaType === 'video') { + var videoParams = {}; + if (typeof bid.video === 'object') { + Object.assign(videoParams, bid.video); + } + Object.assign(videoParams, params); + _requestBid(invVastUrl, params); + } else { + _requestBid(invUrl, params); + } } else { var bidObject = bidfactory.createBid(2); bidObject.bidderCode = 'admixer'; @@ -48,7 +58,13 @@ var AdmixerAdapter = function AdmixerAdapter() { bidObject = bidfactory.createBid(1); bidObject.bidderCode = 'admixer'; bidObject.cpm = bid.cpm; - bidObject.ad = bid.ad; + if (bid.vastUrl) { + bidObject.mediaType = 'video'; + bidObject.vastUrl = bid.vastUrl; + bidObject.descriptionUrl = bid.vastUrl; + } else { + bidObject.ad = bid.ad; + } bidObject.width = bid.width; bidObject.height = bid.height; } else { diff --git a/src/adapters/adyoulike.js b/src/adapters/adyoulike.js new file mode 100644 index 00000000000..7ee65eebe06 --- /dev/null +++ b/src/adapters/adyoulike.js @@ -0,0 +1,201 @@ +import Adapter from 'src/adapters/adapter'; +import bidfactory from 'src/bidfactory'; +import bidmanager from 'src/bidmanager'; +import * as utils from 'src/utils'; +import { format } from 'src/url'; +import { ajax } from 'src/ajax'; +import { STATUS } from 'src/constants'; + +var AdyoulikeAdapter = function AdyoulikeAdapter() { + const _VERSION = '0.1'; + + const baseAdapter = Adapter.createNew('adyoulike'); + + baseAdapter.callBids = function (bidRequest) { + const bidRequests = {}; + const bids = bidRequest.bids || []; + + const validBids = bids.filter(valid); + validBids.forEach(bid => { bidRequests[bid.params.placement] = bid; }); + + const placements = validBids.map(bid => bid.params.placement); + if (!utils.isEmpty(placements)) { + const body = createBody(placements); + const endpoint = createEndpoint(); + ajax(endpoint, + (response) => { + handleResponse(bidRequests, response); + }, body, { + contentType: 'text/json', + withCredentials: true + }); + } + }; + + /* Create endpoint url */ + function createEndpoint() { + return format({ + protocol: (document.location.protocol === 'https:') ? 'https' : 'http', + host: 'hb-api.omnitagjs.com', + pathname: '/hb-api/prebid', + search: createEndpointQS() + }); + } + + /* Create endpoint query string */ + function createEndpointQS() { + const qs = {}; + + const ref = getReferrerUrl(); + if (ref) { + qs.RefererUrl = encodeURIComponent(ref); + } + + const can = getCanonicalUrl(); + if (can) { + qs.CanonicalUrl = encodeURIComponent(can); + } + + return qs; + } + + /* Create request body */ + function createBody(placements) { + const body = { + Version: _VERSION, + Placements: placements, + }; + + // performance isn't supported by mobile safari iOS7. window.performance works, but + // evaluates to true on a unit test which expects false. + // + // try/catch was added to un-block the Prebid 0.25 release, but the adyoulike adapter + // maintainers should revisit this and see if it's really what they want. + try { + if (performance && performance.navigation) { + body.PageRefreshed = performance.navigation.type === performance.navigation.TYPE_RELOAD; + } + } catch (e) { + body.PageRefreshed = false; + } + + return JSON.stringify(body); + } + + /* Response handler */ + function handleResponse(bidRequests, response) { + let responses = []; + try { + responses = JSON.parse(response); + } catch (error) { utils.logError(error); } + + const bidResponses = {}; + responses.forEach(response => { + bidResponses[response.Placement] = response; + }); + + Object.keys(bidRequests).forEach(placement => { + addResponse(placement, bidRequests[placement], bidResponses[placement]); + }); + } + + /* Check that a bid has required parameters */ + function valid(bid) { + const sizes = getSize(bid.sizes); + if (!bid.params.placement || !sizes.width || !sizes.height) { + return false; + } + return true; + } + + /* Get current page referrer url */ + function getReferrerUrl() { + let referer = ''; + if (window.self !== window.top) { + try { + referer = window.top.document.referrer; + } catch (e) { } + } else { + referer = document.referrer; + } + return referer; + } + + /* Get current page canonical url */ + function getCanonicalUrl() { + let link; + if (window.self !== window.top) { + try { + link = window.top.document.head.querySelector('link[rel="canonical"][href]'); + } catch (e) { } + } else { + link = document.head.querySelector('link[rel="canonical"][href]'); + } + + if (link) { + return link.href; + } + return ''; + } + + /* Get parsed size from request size */ + function getSize(requestSizes) { + const parsed = {}, + size = utils.parseSizesInput(requestSizes)[0]; + + if (typeof size !== 'string') { + return parsed; + } + + const parsedSize = size.toUpperCase().split('X'); + const width = parseInt(parsedSize[0], 10); + if (width) { + parsed.width = width; + } + + const height = parseInt(parsedSize[1], 10); + if (height) { + parsed.height = height; + } + + return parsed; + } + + /* Create bid from response */ + function createBid(placementId, bidRequest, response) { + let bid; + if (!response || !response.Banner) { + bid = bidfactory.createBid(STATUS.NO_BID, bidRequest); + } else { + bid = bidfactory.createBid(STATUS.GOOD, bidRequest); + const size = getSize(bidRequest.sizes); + bid.width = size.width; + bid.height = size.height; + bid.cpm = response.Price; + bid.ad = response.Banner; + } + + bid.bidderCode = baseAdapter.getBidderCode(); + + return bid; + } + + /* Add response to bidmanager */ + function addResponse(placementId, bidRequest, response) { + const bid = createBid(placementId, bidRequest, response); + const placement = bidRequest.placementCode; + bidmanager.addBidResponse(placement, bid); + } + + return { + createNew: AdyoulikeAdapter.createNew, + callBids: baseAdapter.callBids, + setBidderCode: baseAdapter.setBidderCode, + }; +}; + +AdyoulikeAdapter.createNew = function () { + return new AdyoulikeAdapter(); +}; + +module.exports = AdyoulikeAdapter; diff --git a/src/adapters/analytics/aolPartnersIds.json b/src/adapters/analytics/aolPartnersIds.json index 17ccf9b4fe3..081fc27ebbc 100644 --- a/src/adapters/analytics/aolPartnersIds.json +++ b/src/adapters/analytics/aolPartnersIds.json @@ -74,5 +74,9 @@ "quantcast": 73, "prebidServer": 74, "onemobile": 75, - "onedisplay": 76 + "onedisplay": 76, + "adyoulike": 77, + "carambola": 78, + "cox": 79, + "eplanning":80 } diff --git a/src/adapters/aol.js b/src/adapters/aol.js index 786321c3338..95b82bbd6eb 100644 --- a/src/adapters/aol.js +++ b/src/adapters/aol.js @@ -231,8 +231,8 @@ const AolAdapter = function AolAdapter() { let formattedPixels = response.ext.pixels.replace(/<\/?script( type=('|")text\/javascript('|")|)?>/g, ''); ad += ''; + 'parent.$$PREBID_GLOBAL$$.aolGlobals.pixelsDropped=true;' + formattedPixels + + '}'; } } @@ -264,8 +264,8 @@ const AolAdapter = function AolAdapter() { if (_isNexageBidder(bid.bidder) && bid.params.id && bid.params.imp && bid.params.imp[0]) { let imp = bid.params.imp[0]; return imp.id && imp.tagid && - ((imp.banner && imp.banner.w && imp.banner.h) || - (imp.video && imp.video.mimes && imp.video.minduration && imp.video.maxduration)); + ((imp.banner && imp.banner.w && imp.banner.h) || + (imp.video && imp.video.mimes && imp.video.minduration && imp.video.maxduration)); } } diff --git a/src/adapters/appnexus.js b/src/adapters/appnexus.js index 0dea42daf41..fee88da7bf3 100644 --- a/src/adapters/appnexus.js +++ b/src/adapters/appnexus.js @@ -43,10 +43,7 @@ AppNexusAdapter = function AppNexusAdapter() { var query = utils.getBidIdParameter('query', bid.params); var referrer = utils.getBidIdParameter('referrer', bid.params); var altReferrer = utils.getBidIdParameter('alt_referrer', bid.params); - - // build our base tag, based on if we are http or https - - var jptCall = 'http' + (document.location.protocol === 'https:' ? 's://secure.adnxs.com/jpt?' : '://ib.adnxs.com/jpt?'); + var jptCall = '//ib.adnxs.com/jpt?'; jptCall = utils.tryAppendQueryString(jptCall, 'callback', '$$PREBID_GLOBAL$$.handleAnCB'); jptCall = utils.tryAppendQueryString(jptCall, 'callback_uid', callbackId); diff --git a/src/adapters/appnexusAst.js b/src/adapters/appnexusAst.js index a3bd0e72441..4ad2fe4502c 100644 --- a/src/adapters/appnexusAst.js +++ b/src/adapters/appnexusAst.js @@ -269,18 +269,17 @@ function AppnexusAstAdapter() { } function outstreamRender(bid) { - window.ANOutstreamVideo.renderAd({ - tagId: bid.adResponse.tag_id, - sizes: [bid.getSize().split('x')], - targetId: bid.adUnitCode, // target div id to render video - uuid: bid.adResponse.uuid, - adResponse: bid.adResponse, - rendererOptions: bid.renderer.getConfig() - }, handleOutstreamRendererEvents.bind(bid)); - } - - function onOutstreamRendererLoaded() { - // setup code for renderer, if any + // push to render queue because ANOutstreamVideo may not be loaded yet + bid.renderer.push(() => { + window.ANOutstreamVideo.renderAd({ + tagId: bid.adResponse.tag_id, + sizes: [bid.getSize().split('x')], + targetId: bid.adUnitCode, // target div id to render video + uuid: bid.adResponse.uuid, + adResponse: bid.adResponse, + rendererOptions: bid.renderer.getConfig() + }, handleOutstreamRendererEvents.bind(bid)); + }); } function handleOutstreamRendererEvents(id, eventName) { @@ -313,9 +312,8 @@ function AppnexusAstAdapter() { id: ad.renderer_id, url: ad.renderer_url, config: { adText: `AppNexus Outstream Video Ad via Prebid.js` }, - callback: () => onOutstreamRendererLoaded.call(null, bid) + loaded: false, }); - try { bid.renderer.setRender(outstreamRender); } catch (err) { diff --git a/src/adapters/carambola.js b/src/adapters/carambola.js new file mode 100644 index 00000000000..18a7c154f6d --- /dev/null +++ b/src/adapters/carambola.js @@ -0,0 +1,193 @@ +/** + * Carambola adapter + */ + +var bidfactory = require('../bidfactory.js'); +var bidmanager = require('../bidmanager.js'); +const utils = require('../utils.js'); +const ajax = require('../ajax.js').ajax; + +const CarambolaAdapter = function CarambolaAdapter() { + const BIDDER_CODE = 'carambola'; + const REQUEST_PATH = 'hb/inimage/getHbBIdProcessedResponse'; + + function _addErrorBidResponse(bid, response = {}, errorMsg = '') { + const bidResponse = bidfactory.createBid(2, bid); + bidResponse.bidderCode = BIDDER_CODE; + bidResponse.reason = errorMsg; + bidmanager.addBidResponse(_getCustomAdUnitCode(bid), bidResponse); + } + // looking at the utils.js at getBidderRequest method. this is what is requested. + function _getCustomAdUnitCode(bid) { + return bid.placementCode; + } + + function _addBidResponse(bid, response) { + const bidResponse = bidfactory.createBid(1, bid); + bidResponse.bidderCode = BIDDER_CODE; + bidResponse.ad = response.ad; + bidResponse.cpm = response.cpm; + bidResponse.width = response.width; + bidResponse.height = response.height; + bidResponse.currencyCode = response.cur; + bidResponse.token = response.token; + bidResponse.pvid = response.pageViewId; + + bidmanager.addBidResponse(_getCustomAdUnitCode(bid), bidResponse); + } + + function _getPageViewId() { + window.Cbola = window.Cbola || {}; + window.Cbola.HB = window.Cbola.HB || {}; + window.Cbola.HB.pvid = window.Cbola.HB.pvid || _createPageViewId(); + return window.Cbola.HB.pvid; + } + + function _createPageViewId() { + function _pad(number) { + return number > 9 ? number : '0' + number + } + + const MIN = 10000; + const MAX = 90000; + let now = new Date(); + + var pvid = + _pad(now.getDate()) + + _pad(now.getMonth() + 1) + + _pad(now.getFullYear() % 100) + + _pad(now.getHours()) + + _pad(now.getMinutes()) + + _pad(now.getSeconds()) + + _pad(now.getMilliseconds() % 100) + + Math.floor((Math.random() * MAX) + MIN); + + return pvid; + } + + // sends a request for each bid + function _buildRequest(bids, params) { + if (!utils.isArray(bids)) { + return; + } + // iterate on every bid and return the response to the hb manager + utils._each(bids, bid => { + let tempParams = params || {}; + tempParams.cbolaMode = bid.params.cbolaMode || 0; + tempParams.wid = bid.params.wid || 0; + tempParams.pixel = bid.params.pixel || ''; + tempParams.bidFloor = bid.params.bidFloor || 0; + tempParams.pageViewId = _getPageViewId(); + tempParams.hb_token = utils.generateUUID(); + tempParams.sizes = utils.parseSizesInput(bid.sizes) + ''; + tempParams.bidsCount = bids.length; + + for (let customParam in bid.params.customParams) { + if (bid.params.customParams.hasOwnProperty(customParam)) { + tempParams['c_' + customParam] = bid.params.customParams[customParam]; + } + } + + let server = bid.params.server || 'route.carambo.la'; + let cbolaHbApiUrl = '//' + server + '/' + REQUEST_PATH; + + // the responses of the bid requests + ajax(cbolaHbApiUrl + _jsonToQueryString(tempParams), response => { + // no response + if (!response || response.cpm <= 0) { + utils.logError('Empty bid response', BIDDER_CODE, bid); + _addErrorBidResponse(bid, response, 'Empty bid response'); + return; + } + try { + response = JSON.parse(response); + if (response && response.cpm <= 0) + { + utils.logError('Bid response returned 0', BIDDER_CODE, bid); + _addErrorBidResponse(bid, response, 'Bid response returned 0'); + return; + } + } catch (e) { + utils.logError('Invalid JSON in bid response', BIDDER_CODE, bid); + _addErrorBidResponse(bid, response, 'Invalid JSON in bid response'); + return; + } + _addBidResponse(bid, response); + }, null, {method: 'GET'}); + }); + } + + // build the genral request to the server + function _callBids(params) { + let isIfr, + bids = params.bids || [], + currentURL = (window.parent !== window) ? document.referrer : window.location.href; + currentURL = currentURL && encodeURIComponent(currentURL); + try { + isIfr = window.self !== window.top; + } + catch (e) { + isIfr = false; + } + if (bids.length === 0) { + return; + } + + _buildRequest(bids, { + pageUrl: currentURL, + did: bids[0].params.did || 0, + pid: bids[0].params.pid || '', + res: _getScreenSize(screen), + ifr: isIfr, + viewPortDim: _getViewportDimensions(isIfr) + }); + } + + function _getScreenSize(screen) { + return screen ? `${screen.width}x${screen.height}x${screen.colorDepth}` : '0'; + } + + function _getViewportDimensions(isIfr) { + let width, + height, + tWin = window, + tDoc = document, + docEl = tDoc.documentElement, + body; + + if (isIfr) { + try { + tWin = window.top; + tDoc = window.top.document; + } + catch (e) { + return; + } + docEl = tDoc.documentElement; + body = tDoc.body; + width = tWin.innerWidth || docEl.clientWidth || body.clientWidth; + height = tWin.innerHeight || docEl.clientHeight || body.clientHeight; + } else { + docEl = tDoc.documentElement; + width = tWin.innerWidth || docEl.clientWidth; + height = tWin.innerHeight || docEl.clientHeight; + } + return `${width}x${height}`; + } + + function _jsonToQueryString(json) { + return '?' + + Object.keys(json).map(function(key) { + return encodeURIComponent(key) + '=' + + encodeURIComponent(json[key]); + }).join('&'); + } + + // Export the `callBids` function, so that Prebid.js can execute + // this function when the page asks to send out bid requests. + return { + callBids: _callBids + }; +}; + +module.exports = CarambolaAdapter; diff --git a/src/adapters/conversant.js b/src/adapters/conversant.js index d8c15c1d7e7..974eb4c8100 100644 --- a/src/adapters/conversant.js +++ b/src/adapters/conversant.js @@ -1,21 +1,21 @@ 'use strict'; -var VERSION = '2.0.1', - CONSTANTS = require('../constants.json'), - utils = require('../utils.js'), - bidfactory = require('../bidfactory.js'), - bidmanager = require('../bidmanager.js'), - adloader = require('../adloader'), - ajax = require('../ajax').ajax; +var VERSION = '2.1.0'; +var CONSTANTS = require('../constants.json'); +var utils = require('../utils.js'); +var bidfactory = require('../bidfactory.js'); +var bidmanager = require('../bidmanager.js'); +var adloader = require('../adloader'); +var ajax = require('../ajax').ajax; /** * Adapter for requesting bids from Conversant */ var ConversantAdapter = function () { - var w = window, - n = navigator; + var w = window; + var n = navigator; // production endpoint - var conversantUrl = '//media.msg.dotomi.com/s2s/header?callback=$$PREBID_GLOBAL$$.conversantResponse'; + var conversantUrl = '//media.msg.dotomi.com/s2s/header/24?callback=$$PREBID_GLOBAL$$.conversantResponse'; // SSAPI returns JSONP with window.pbjs.conversantResponse as the cb var appendScript = function (code) { @@ -55,21 +55,26 @@ var ConversantAdapter = function () { var requestBids = function (bidReqs) { // build bid request object - var page = location.pathname + location.search + location.hash, - siteId = '', - conversantImps = [], - conversantBidReqs, - secure = 0; + var page = location.pathname + location.search + location.hash; + var siteId = ''; + var conversantImps = []; + var conversantBidReqs; + var secure = 0; // build impression array for conversant utils._each(bidReqs, function (bid) { - var bidfloor = utils.getBidIdParameter('bidfloor', bid.params), - adW = 0, - adH = 0, - imp; + var bidfloor = utils.getBidIdParameter('bidfloor', bid.params); + var adW = 0; + var adH = 0; + var format; + var tagId; + var pos; + var imp; secure = utils.getBidIdParameter('secure', bid.params) ? 1 : secure; siteId = utils.getBidIdParameter('site_id', bid.params) + ''; + tagId = utils.getBidIdParameter('tag_id', bid.params); + pos = utils.getBidIdParameter('position', bid.params); // Allow sizes to be overridden per placement var bidSizes = Array.isArray(bid.params.sizes) ? bid.params.sizes : bid.sizes; @@ -78,22 +83,69 @@ var ConversantAdapter = function () { adW = bidSizes[0]; adH = bidSizes[1]; } else { - adW = bidSizes[0][0]; - adH = bidSizes[0][1]; + format = []; + utils._each(bidSizes, function (bidSize) { + format.push({ + w: bidSize[0], + h: bidSize[1] + }); + }); } imp = { id: bid.bidId, - banner: { - w: adW, - h: adH - }, secure: secure, bidfloor: bidfloor || 0, displaymanager: 'Prebid.js', displaymanagerver: VERSION }; + if (tagId !== '') { + imp.tagid = tagId; + } + + if (bid.mediaType === 'video') { + var mimes = []; + var maxduration = 0; + var protocols = []; + var api = []; + + var video = Array.isArray(format) ? {format: format} : {w: adW, h: adH}; + + mimes = utils.getBidIdParameter('mimes', bid.params); + if (mimes !== '') { + video.mimes = mimes; + } + + maxduration = utils.getBidIdParameter('maxduration', bid.params); + if (maxduration !== '') { + video.maxduration = maxduration; + } + + protocols = utils.getBidIdParameter('protocols', bid.params); + if (protocols !== '') { + video.protocols = protocols; + } + + api = utils.getBidIdParameter('api', bid.params); + if (api !== '') { + video.api = api; + } + + if (pos !== '') { + video.pos = pos; + } + + imp.video = video; + } else { + var banner = Array.isArray(format) ? {format: format} : {w: adW, h: adH}; + + if (pos !== '') { + banner.pos = pos; + } + imp.banner = banner; + } + conversantImps.push(imp); }); @@ -135,13 +187,13 @@ var ConversantAdapter = function () { var parseSeatbid = function (bidResponse) { var placementsWithBidsBack = []; utils._each(bidResponse.bid, function (conversantBid) { - var responseCPM, - placementCode = '', - id = conversantBid.impid, - bid = {}, - responseAd, - responseNurl, - sizeArrayLength; + var responseCPM; + var placementCode = ''; + var id = conversantBid.impid; + var bid = {}; + var responseAd; + var responseNurl; + var sizeArrayLength; // Bid request we sent Conversant var bidRequested = $$PREBID_GLOBAL$$._bidsRequested.find(bidSet => bidSet.bidderCode === 'conversant').bids.find(bid => bid.bidId === id); @@ -162,11 +214,14 @@ var ConversantAdapter = function () { bid = bidfactory.createBid(1, bidRequested); bid.creative_id = conversantBid.id || ''; bid.bidderCode = 'conversant'; - bid.cpm = responseCPM; - // Track impression image onto returned html - bid.ad = responseAd + ''; + if (bidRequested.mediaType === 'video') { + bid.vastUrl = responseAd; + } else { + // Track impression image onto returned html + bid.ad = responseAd + ''; + } sizeArrayLength = bidRequested.sizes.length; if (sizeArrayLength === 2 && typeof bidRequested.sizes[0] === 'number' && typeof bidRequested.sizes[1] === 'number') { diff --git a/src/adapters/cox.js b/src/adapters/cox.js new file mode 100644 index 00000000000..e0e2a053251 --- /dev/null +++ b/src/adapters/cox.js @@ -0,0 +1,255 @@ +var bidfactory = require('../bidfactory.js'); +var bidmanager = require('../bidmanager.js'); +var adLoader = require('../adloader.js'); + +var CoxAdapter = function CoxAdapter() { + var adZoneAttributeKeys = ['id', 'size', 'thirdPartyClickUrl'], + otherKeys = ['siteId', 'wrapper', 'referrerUrl'], + placementMap = {}, + W = window; + + var COX_BIDDER_CODE = 'cox'; + + function _callBids(params) { + var env = ''; + + // Create global cdsTag and CMT object (for the latter, only if needed ) + W.cdsTag = {}; + if (!W.CMT) W.CMT = _getCoxLite(); + + // Populate the tag with the info from prebid + var bids = params.bids || [], + tag = W.cdsTag, + i, + j; + for (i = 0; i < bids.length; i++) { + var bid = bids[i], + cfg = bid.params || {}; + + if (cfg.id) { + tag.zones = tag.zones || {}; + var zone = {}; + + for (j = 0; j < adZoneAttributeKeys.length; j++) { + if (cfg[adZoneAttributeKeys[j]]) zone[adZoneAttributeKeys[j]] = cfg[adZoneAttributeKeys[j]]; + } + for (j = 0; j < otherKeys.length; j++) { + if (cfg[otherKeys[j]]) tag[otherKeys[j]] = cfg[otherKeys[j]]; + } + var adZoneKey = 'as' + cfg.id; + tag.zones[adZoneKey] = zone; + + // Check for an environment setting + if (cfg.env) env = cfg.env; + + // Update the placement map + var xy = (cfg.size || '0x0').split('x'); + placementMap[adZoneKey] = { + p: bid.placementCode, + w: xy[0], + h: xy[1] + }; + } + } + if (tag.zones && Object.keys(tag.zones).length > 0) { + tag.__callback__ = function (r) { + tag.response = r; + _notify(); + }; + adLoader.loadScript(W.CMT.Service.buildSrc(tag, env)); + } + } + + function _notify() { + // Will execute in the context of a bid + // function finalizeAd(price) { + // this.ad = W.CMT.Service.setAuctionPrice(this.ad, price); + // return this; + // } + + for (var adZoneKey in placementMap) { + var bid = W.CMT.Service.getBidTrue(adZoneKey), + bidObj, + data = placementMap[adZoneKey]; + + if (bid > 0) { + bidObj = bidfactory.createBid(1); + bidObj.cpm = bid; + bidObj.ad = W.CMT.Service.getAd(adZoneKey); + bidObj.width = data.w; + bidObj.height = data.h; + // bidObj.floor = W.CMT.Service.getSecondPrice(adZoneKey); + // bidObj.finalizeAd = finalizeAd; + } else { + bidObj = bidfactory.createBid(2); + } + bidObj.bidderCode = COX_BIDDER_CODE; + bidmanager.addBidResponse(data.p, bidObj); + } + } + + function _getCoxLite() { + var CMT = {}; + + CMT.Util = (function () { + return { + + getRand: function getRand() { + return Math.round(Math.random() * 100000000); + }, + + encodeUriObject: function encodeUriObject(obj) { + return encodeURIComponent(JSON.stringify(obj)); + }, + + extractUrlInfo: function extractUrlInfo() { + function f2(callback) { + try { + if (!W.location.ancestorOrigins) return; + for (var i = 0, len = W.location.ancestorOrigins.length; len > i; i++) { + callback.call(null, W.location.ancestorOrigins[i], i); + } + } catch (ignore) { } + return []; + } + + function f1(callback) { + var oneWindow, + infoArray = []; + do { + try { + oneWindow = oneWindow ? oneWindow.parent : W; + callback.call(null, oneWindow, infoArray); + } catch (t) { + infoArray.push({ + referrer: null, + location: null, + isTop: !1 + }); + return infoArray; + } + } while (oneWindow !== W.top); + return infoArray; + } + var allInfo = f1(function (oneWindow, infoArray) { + try { + infoArray.push({ referrer: oneWindow.document.referrer || null, location: oneWindow.location.href || null, isTop: oneWindow === W.top }); + } catch (e) { + infoArray.push({ referrer: null, location: null, isTop: oneWindow === W.top }); + } + }); + f2(function (n, r) { + allInfo[r].ancestor = n; + }); + for (var t = '', e = !1, i = allInfo.length - 1, l = allInfo.length - 1; l >= 0; l--) { + t = allInfo[l].location; + if (!t && l > 0) { + t = allInfo[l - 1].referrer; + if (!t) t = allInfo[l - 1].ancestor; + if (t) { + e = W.location.ancestorOrigins ? !0 : l === allInfo.length - 1 && allInfo[allInfo.length - 1].isTop; + break; + } + } + } return { url: t, isTop: e, depth: i }; + }, + + srTestCapabilities: function srTestCapabilities() { + var plugins = navigator.plugins, + flashVer = -1, + sf = 'Shockwave Flash'; + + if (plugins && plugins.length > 0) { + if (plugins[sf + ' 2.0'] || plugins[sf]) { + var swVer2 = plugins[sf + ' 2.0'] ? ' 2.0' : ''; + var flashDescription = plugins[sf + swVer2].description; + flashVer = flashDescription.split(' ')[2].split('.')[0]; + } + } + if (flashVer > 4) return 15; else return 7; + } + + }; + }()); + + // Ad calling functionality + CMT.Service = (function () { + // Closure variables shared by the service functions + var U = CMT.Util; + + return { + + buildSrc: function buildSrc(tag, env) { + var src = (document.location.protocol === 'https:' ? 'https://' : 'http://') + (!env || env === 'PRD' ? '' : env === 'PPE' ? 'ppe-' : env === 'STG' ? 'staging-' : '') + 'ad.afy11.net/ad' + '?mode=11' + '&ct=' + U.srTestCapabilities() + '&nif=0' + '&sf=0' + '&sfd=0' + '&ynw=0' + '&rand=' + U.getRand() + '&hb=1' + '&rk1=' + U.getRand() + '&rk2=' + new Date().valueOf() / 1000; + + // Make sure we don't have a response object... + delete tag.response; + + // Extracted url info... + var urlInfo = U.extractUrlInfo(); + tag.pageUrl = urlInfo.url; + tag.puTop = urlInfo.isTop; + + // Attach the serialized tag to our string + src += '&ab=' + U.encodeUriObject(tag); + + return src; + }, + + getAd: function (zoneKey) { + if (!zoneKey) return; + + return this._getData(zoneKey, 'ad') + (this._getResponse().tpCookieSync || ''); // ...also append cookie sync if present + }, + + // getSecondPrice: function getSecondPrice(zoneKey) { + // if (zoneKey.substring(0, 2) !== 'as') zoneKey = 'as' + zoneKey; + // var bid = this.getBidTrue(zoneKey), + // floor = this._getData(zoneKey, 'floor'); + + // // If no floor, just set it to 80% of the bid + // if (!floor) floor = bid * 0.80; + + // // Adjust the floor if it's too high...it needs to always be lower + // if (floor >= bid) { + // floor = floor * 0.80; // Take off 20% to account for possible non-adjusted 2nd highest bid + + // // If it's still too high, just take 80% to 90% of the bid + // if (floor >= bid) floor = bid * ((Math.random() * 10) + 80) / 100; + // } + // return Math.round(floor * 100) / 100; + // }, + + // setAuctionPrice: function setAuctionPrice(ad, bid) { + // return ad ? ad.replace('${AUCTION_PRICE}', bid) : ad; + // }, + + getBidTrue: function getBidTrue(zoneKey) { + return Math.round(this._getData(zoneKey, 'price') * 100) / 100; + }, + + _getData: function (zoneKey, field) { + var response = this._getResponse(), + zoneResponseData = response.zones ? response.zones[zoneKey] : {}; + + return (zoneResponseData || {})[field] || null; + }, + + _getResponse: function () { + var tag = W.cdsTag; + return (tag && tag.response) ? tag.response : {}; + }, + }; + }()); + + return CMT; + } + + // Export the callBids function, so that prebid.js can execute this function + // when the page asks to send out bid requests. + return { + callBids: _callBids, + }; +}; + +module.exports = CoxAdapter; diff --git a/src/adapters/criteo.js b/src/adapters/criteo.js index aeea56f0851..c0e14fb3d51 100644 --- a/src/adapters/criteo.js +++ b/src/adapters/criteo.js @@ -3,7 +3,8 @@ var bidmanager = require('../bidmanager.js'); var adloader = require('../adloader'); var CriteoAdapter = function CriteoAdapter() { - var _publisherTagUrl = window.location.protocol + '//static.criteo.net/js/ld/publishertag.js'; + var sProt = (window.location.protocol === 'http:') ? 'http:' : 'https:'; + var _publisherTagUrl = sProt + '//static.criteo.net/js/ld/publishertag.js'; var _bidderCode = 'criteo'; var _profileId = 125; diff --git a/src/adapters/eplanning.js b/src/adapters/eplanning.js new file mode 100644 index 00000000000..fd4acc0e047 --- /dev/null +++ b/src/adapters/eplanning.js @@ -0,0 +1,280 @@ +var bidfactory = require('src/bidfactory.js'); +var bidmanager = require('src/bidmanager.js'); + +var EPlanningAdapter = function EPlanningAdapter() { + (function() { + var win = window, doc = win.document, pbjs = win.pbjs, _global = {}, _default = { 'sv': 'ads.us.e-planning.net', 't': 0 }, rnd, FILE = 'file', CALLBACK_FUNCTION = 'hbpb.rH', NULL_SIZE = '1x1', _csRequested = [], PROTO = location.protocol === 'https:' ? 'https:' : 'http:', ISV = 'aklc.img.e-planning.net'; + function Hbpb() { + var slots = (function() { + var _data = []; + function Slot(slotId) { + var data = _data[slotId]; + function hasAds() { + return _data[slotId].ads.length; + } + function getSizes() { + return data.sizes; + } + function getSizesString() { + var s = [], i, sizes = getSizes(); + if (sizes && sizes.length) { + if (typeof sizes[0] === 'object') { + for (i = 0; i < sizes.length; i++) { + s.push(sizes[i][0] + 'x' + sizes[i][1]); + } + } else { + s.push(sizes[0] + 'x' + sizes[1]); + } + } else { + return NULL_SIZE; + } + return s.join(','); + } + return { + getPlacementCode: function() { + return data.placementCode; + }, + getString: function() { + return this.getPlacementCode() + ':' + getSizesString(); + }, + addAd: function(ad) { + _data[slotId].ads.push(ad); + }, + getFormatedResponse: function() { + var ad, that = this; + if (hasAds()) { + ad = data.ads[0]; + return { + 'placementCode': that.getPlacementCode(), + 'ad': { + 'ad': ad.adm, + 'cpm': ad.pr, + 'width': ad.size.w, + 'height': ad.size.h + } + }; + } else { + return { 'placementCode': that.getPlacementCode() }; + } + } + }; + } + function findAll() { + var i = 0, r = []; + for (i = 0; i < _data.length; i++) { + r.push(new Slot(i)); + } + return r; + } + return { + add: function(slot) { + slot.ads = []; + _data.push(slot); + }, + get: function(slotId) { + return new Slot(slotId); + }, + getString: function() { + var _slots = [], i, slot; + for (i = 0; i < _data.length; i++) { + slot = this.get(i); + _slots.push(slot.getString()); + } + return _slots.join('+'); + }, + findByPlacementCode: function(placementCode) { + var i, _slots = findAll(); + for (i = 0; i < _slots.length; i++) { + if (_slots[i].getPlacementCode() === placementCode) { + return _slots[i]; + } + } + }, + getFormatedResponse: function() { + var _slots = findAll(), i, r = []; + for (i = 0; i < _slots.length; i++) { + r.push(_slots[i].getFormatedResponse()); + } + return { + 'bids': r + }; + } + }; + })(); + function call(params) { + var i, bids = params.bids; + for (i = 0; i < bids.length; i++) { + slots.add({ + _raw: bids[i], + placementCode: bids[i].placementCode, + sizes: bids[i].sizes + }); + setGlobalParam('sv', bids[i]); + setGlobalParam('ci', bids[i]); + setGlobalParam('t', bids[i]); + } + doRequest(); + } + function setGlobalParam(param, bid) { + if (!_global[param]) { + if (bid && bid.params && bid.params[param]) { + _global[param] = bid.params[param]; + } + } + } + function getGlobalParam(param) { + return (_global[param] || _default[param]); + } + function getRandom() { + if (!rnd) { + rnd = Math.random(); + } + return rnd; + } + function getDocURL() { + return escape(win.location.href || FILE); + } + function getReferrerURL() { + return doc.referrer; + } + function getCallbackFunction() { + return CALLBACK_FUNCTION; + } + function doRequest() { + var clienteId = getGlobalParam('ci'), url, dfpClienteId = '1', sec = 'ROS', params = [], t = getGlobalParam('t'); + if (clienteId && !t) { + url = PROTO + '//' + getGlobalParam('sv') + '/hb/1/' + clienteId + '/' + dfpClienteId + '/' + (win.location.hostname || FILE) + '/' + sec + '?'; + params.push('rnd=' + getRandom()); + params.push('e=' + slots.getString()); + if (getDocURL()) { + params.push('ur=' + getDocURL()); + } + if (getReferrerURL()) { + params.push('fr=' + getReferrerURL()); + } + params.push('cb=' + getCallbackFunction()); + params.push('r=pbjs'); + url += params.join('&'); + load(url); + } else if (t) { + url = PROTO + '//' + ISV + '/layers/t_pbjs_' + t + '.js'; + load(url); + } + } + function load(url) { + var script = doc.createElement('script'); + script.src = url; + doc.body.appendChild(script); + } + function callback(response) { + if (pbjs && pbjs.processEPlanningResponse && typeof pbjs.processEPlanningResponse === 'function') { + pbjs.processEPlanningResponse(response); + } + } + function syncUsers(cs) { + var i, e, d; + for (i = 0; i < cs.length; i++) { + if (typeof cs[i] === 'string' && _csRequested.indexOf(cs[i]) === -1) { + (new Image()).src = cs[i]; + _csRequested.push(cs[i]); + } else if (typeof cs[i] === 'object' && _csRequested.indexOf(cs[i].u) === -1) { + if (cs[i].j) { + e = doc.createElement('script'); + e.src = cs[i].u; + } else if (cs[i].ifr) { + e = doc.createElement('iframe'); + e.src = cs[i].u; + e.style.width = e.style.height = '1px'; + e.display = 'none'; + } + if (cs[i].data) { + for (d in cs[i].data) { + if (cs[i].data.hasOwnProperty(d)) { + e.setAttribute('data-' + d, cs[i].data[d]); + } + } + } + doc.body.appendChild(e); + _csRequested.push(cs[i].u); + } + } + } + function rH(response) { + var slot, i, o; + if (response && response.sp && response.sp.length) { + for (i = 0; i < response.sp.length; i++) { + if (response.sp[i].a) { + slot = slots.findByPlacementCode(response.sp[i].k); + if (slot) { + for (o = 0; o < response.sp[i].a.length; o++) { + slot.addAd({ + 'adm': response.sp[i].a[o].adm, + 'pr': response.sp[i].a[o].pr, + 'size': { + 'w': response.sp[i].a[o].w, + 'h': response.sp[i].a[o].h + } + }); + } + } + } + } + callback(slots.getFormatedResponse()); + } + if (response && response.cs && response.cs.length) { + syncUsers(response.cs); + } + } + return { + call: function(params) { + return call(params); + }, + rH: function(response) { + return rH(response); + } + }; + } + win.hbpb = win.hbpb || new Hbpb(); + })(); + + window.pbjs = window.pbjs || {}; + window.pbjs.processEPlanningResponse = function(response) { + var bids, bidObject, i; + if (response) { + bids = response.bids; + for (i = 0; i < bids.length; i++) { + if (bids[i].ad) { + bidObject = getBidObject(bids[i]); + bidmanager.addBidResponse(bids[i].placementCode, bidObject); + } else { + bidObject = bidfactory.createBid(2); + bidObject.bidderCode = 'eplanning'; + bidmanager.addBidResponse(bids[i].placementCode, bidObject); + } + } + } + }; + + function getBidObject(bid) { + var bidObject = bidfactory.createBid(1), i; + bidObject.bidderCode = 'eplanning'; + for (i in bid.ad) { + if (bid.ad.hasOwnProperty(i)) { + bidObject[i] = bid.ad[i]; + } + } + return bidObject; + } + + function _callBids(params) { + if (window.hbpb) { + window.hbpb.call(params); + } + } + + return { + callBids: _callBids + }; +}; + +module.exports = EPlanningAdapter; diff --git a/src/adapters/gumgum.js b/src/adapters/gumgum.js index 1d589fa7041..4196287c520 100644 --- a/src/adapters/gumgum.js +++ b/src/adapters/gumgum.js @@ -15,6 +15,7 @@ const GumgumAdapter = function GumgumAdapter() { const requestCache = {}; const throttleTable = {}; const defaultThrottle = 3e4; + const dtCredentials = { member: 'YcXr87z2lpbB' }; try { topWindow = global.top; @@ -27,6 +28,22 @@ const GumgumAdapter = function GumgumAdapter() { return new Date().getTime(); } + function _getDigiTrustQueryParams() { + function getDigiTrustId () { + var digiTrustUser = (window.DigiTrust && window.DigiTrust.getUser) ? window.DigiTrust.getUser(dtCredentials) : {}; + return digiTrustUser && digiTrustUser.success && digiTrustUser.identity || ''; + }; + + let digiTrustId = getDigiTrustId(); + // Verify there is an ID and this user has not opted out + if (!digiTrustId || digiTrustId.privacy && digiTrustId.privacy.optout) { + return {}; + } + return { + 'dt': digiTrustId.id + }; + } + function _callBids({ bids }) { const browserParams = { vw: topWindow.innerWidth, @@ -37,6 +54,7 @@ const GumgumAdapter = function GumgumAdapter() { ce: navigator.cookieEnabled, dpr: topWindow.devicePixelRatio || 1 }; + utils._each(bids, bidRequest => { const { bidId , params = {} @@ -91,7 +109,7 @@ const GumgumAdapter = function GumgumAdapter() { const callback = { jsonp: `$$PREBID_GLOBAL$$.handleGumGumCB['${bidId}']` }; CALLBACKS[bidId] = _handleGumGumResponse(cachedBid); - const query = Object.assign(callback, browserParams, bid); + const query = Object.assign(callback, browserParams, bid, _getDigiTrustQueryParams()); const bidCall = `${bidEndpoint}?${utils.parseQueryStringParameters(query)}`; adloader.loadScript(bidCall); }); diff --git a/src/adapters/openx.js b/src/adapters/openx.js index 009dde53707..6341b860134 100644 --- a/src/adapters/openx.js +++ b/src/adapters/openx.js @@ -218,7 +218,6 @@ const OpenxAdapter = function OpenxAdapter() { ifr: isIfr, tz: startTime.getTimezoneOffset(), tws: getViewportDimensions(isIfr), - ee: 'api_sync_write', ef: 'bt%2Cdb', be: 1, bc: BIDDER_CONFIG diff --git a/src/adapters/prebidServer.js b/src/adapters/prebidServer.js index dc83a04019b..5dc78008060 100644 --- a/src/adapters/prebidServer.js +++ b/src/adapters/prebidServer.js @@ -108,34 +108,12 @@ function PrebidServer() { if (result.status === 'OK') { if (result.bidder_status) { result.bidder_status.forEach(bidder => { - if (bidder.no_bid || bidder.no_cookie) { - // store a "No Bid" bid response - - if (!bidder.ad_unit) { - utils.getBidderRequestAllAdUnits(bidder.bidder).bids.forEach(bid => { - let bidObject = bidfactory.createBid(STATUS.NO_BID, bid); - bidObject.adUnitCode = bid.placementCode; - bidObject.bidderCode = bidder.bidder; - - bidmanager.addBidResponse(bid.placementCode, bidObject); - }); - } else { - let bidObject = bidfactory.createBid(STATUS.NO_BID, { - bidId: bidder.bid_id - }); - - bidObject.adUnitCode = bidder.ad_unit; - bidObject.bidderCode = bidder.bidder; - - bidmanager.addBidResponse(bidObject.adUnitCode, bidObject); - } - } if (bidder.no_cookie) { - // if no cookie is present then no bids were made, we don't store a bid response queueSync({bidder: bidder.bidder, url: bidder.usersync.url, type: bidder.usersync.type}); } }); } + if (result.bids) { result.bids.forEach(bidObj => { let bidRequest = utils.getBidRequest(bidObj.bid_id); @@ -154,10 +132,30 @@ function PrebidServer() { bidObject.ad = bidObj.adm; bidObject.width = bidObj.width; bidObject.height = bidObj.height; + if (bidObj.deal_id) { + bidObject.dealId = bidObj.deal_id; + } bidmanager.addBidResponse(bidObj.code, bidObject); }); } + + const receivedBidIds = result.bids ? result.bids.map(bidObj => bidObj.bid_id) : []; + + // issue a no-bid response for every bid request that can not be matched with received bids + config.bidders.forEach(bidder => { + utils + .getBidderRequestAllAdUnits(bidder) + .bids.filter(bidRequest => !receivedBidIds.includes(bidRequest.bidId)) + .forEach(bidRequest => { + let bidObject = bidfactory.createBid(STATUS.NO_BID, bidRequest); + + bidObject.adUnitCode = bidRequest.placementCode; + bidObject.bidderCode = bidRequest.bidder; + + bidmanager.addBidResponse(bidObject.adUnitCode, bidObject); + }); + }); } else if (result.status === 'no_cookie') { // cookie sync diff --git a/src/adapters/pubmatic.js b/src/adapters/pubmatic.js index 44fb9bf97fc..5caa286eed9 100644 --- a/src/adapters/pubmatic.js +++ b/src/adapters/pubmatic.js @@ -11,6 +11,9 @@ var bidmanager = require('../bidmanager.js'); var PubmaticAdapter = function PubmaticAdapter() { var bids; var _pm_pub_id; + var _pm_pub_age; + var _pm_pub_gender; + var _pm_pub_kvs; var _pm_optimize_adslots = []; let iframe; @@ -21,6 +24,9 @@ var PubmaticAdapter = function PubmaticAdapter() { var bid = bids[i]; // bidmanager.pbCallbackMap['' + bid.params.adSlot] = bid; _pm_pub_id = _pm_pub_id || bid.params.publisherId; + _pm_pub_age = _pm_pub_age || (bid.params.age || ''); + _pm_pub_gender = _pm_pub_gender || (bid.params.gender || ''); + _pm_pub_kvs = _pm_pub_kvs || (bid.params.kvs || ''); _pm_optimize_adslots.push(bid.params.adSlot); } @@ -51,11 +57,18 @@ var PubmaticAdapter = function PubmaticAdapter() { content += '' + 'window.pm_pub_id = "%%PM_PUB_ID%%";' + 'window.pm_optimize_adslots = [%%PM_OPTIMIZE_ADSLOTS%%];' + + 'window.kaddctr = "%%PM_ADDCTR%%";' + + 'window.kadgender = "%%PM_GENDER%%";' + + 'window.kadage = "%%PM_AGE%%";' + 'window.pm_async_callback_fn = "window.parent.$$PREBID_GLOBAL$$.handlePubmaticCallback";'; + content += ''; var map = {}; map.PM_PUB_ID = _pm_pub_id; + map.PM_ADDCTR = _pm_pub_kvs; + map.PM_GENDER = _pm_pub_gender; + map.PM_AGE = _pm_pub_age; map.PM_OPTIMIZE_ADSLOTS = _pm_optimize_adslots.map(function (adSlot) { return "'" + adSlot + "'"; }).join(','); diff --git a/src/adapters/rubicon.js b/src/adapters/rubicon.js index 0b30e7ce771..241e30c6fdf 100644 --- a/src/adapters/rubicon.js +++ b/src/adapters/rubicon.js @@ -28,6 +28,7 @@ var sizeMap = { 8: '120x600', 9: '160x600', 10: '300x600', + 14: '250x250', 15: '300x250', 16: '336x280', 19: '300x100', @@ -45,6 +46,7 @@ var sizeMap = { 57: '970x250', 58: '1000x90', 59: '320x80', + 60: '320x150', 61: '1000x1000', 65: '640x480', 67: '320x480', diff --git a/src/adapters/smartadserver.js b/src/adapters/smartadserver.js index ae5ec38844c..21676beebc1 100644 --- a/src/adapters/smartadserver.js +++ b/src/adapters/smartadserver.js @@ -37,7 +37,7 @@ var SmartAdServer = function SmartAdServer() { var adCall = url.parse(bid.params.domain); adCall.pathname = '/prebid'; adCall.search = { - 'pbjscbk': 'pbjs.' + generateCallback(bid), + 'pbjscbk': '$$PREBID_GLOBAL$$.' + generateCallback(bid), 'siteid': bid.params.siteId, 'pgid': bid.params.pageId, 'fmtid': bid.params.formatId, diff --git a/src/adapters/trion.js b/src/adapters/trion.js index e3c5c34c794..c0320f262c3 100644 --- a/src/adapters/trion.js +++ b/src/adapters/trion.js @@ -58,16 +58,17 @@ TrionAdapter = function TrionAdapter() { var pubId = utils.getBidIdParameter('pubId', bid.params); var sectionId = utils.getBidIdParameter('sectionId', bid.params); var re = utils.getBidIdParameter('re', bid.params); - var url = window.location.href; + var url = utils.getTopWindowUrl(); var sizes = utils.parseSizesInput(bid.sizes).join(','); var trionUrl = BID_REQUEST_BASE_URL; - trionUrl = utils.tryAppendQueryString(trionUrl, 'callback', 'pbjs.handleTrionCB'); + trionUrl = utils.tryAppendQueryString(trionUrl, 'callback', '$$PREBID_GLOBAL$$.handleTrionCB'); trionUrl = utils.tryAppendQueryString(trionUrl, 'bidId', bidId); trionUrl = utils.tryAppendQueryString(trionUrl, 'pubId', pubId); trionUrl = utils.tryAppendQueryString(trionUrl, 'sectionId', sectionId); trionUrl = utils.tryAppendQueryString(trionUrl, 're', re); + trionUrl = utils.tryAppendQueryString(trionUrl, 'slot', bid.placementCode); if (url) { trionUrl += 'url=' + url + '&'; } diff --git a/src/adapters/unruly.js b/src/adapters/unruly.js new file mode 100644 index 00000000000..c9fbfde1ecc --- /dev/null +++ b/src/adapters/unruly.js @@ -0,0 +1,108 @@ +import { ajax } from 'src/ajax' +import bidfactory from 'src/bidfactory' +import bidmanager from 'src/bidmanager' +import * as utils from 'src/utils' +import { STATUS } from 'src/constants' +import { Renderer } from 'src/Renderer' + +function createRenderHandler({ bidResponseBid, rendererConfig }) { + function createApi() { + parent.window.unruly.native.prebid = parent.window.unruly.native.prebid || {} + parent.window.unruly.native.prebid.uq = parent.window.unruly.native.prebid.uq || [] + + return { + render(bidResponseBid) { + parent.window.unruly.native.prebid.uq.push(['render', bidResponseBid]) + }, + onLoaded(bidResponseBid) {} + } + } + + parent.window.unruly = parent.window.unruly || {} + parent.window.unruly.native = parent.window.unruly.native || {} + parent.window.unruly.native.siteId = parent.window.unruly.native.siteId || rendererConfig.siteId + + const api = createApi() + return { + render() { + api.render(bidResponseBid) + }, + onRendererLoad() { + api.onLoaded(bidResponseBid) + } + } +} + +function createBidResponseHandler(bidRequestBids) { + return { + onBidResponse(responseBody) { + try { + const exchangeResponse = JSON.parse(responseBody) + exchangeResponse.bids.forEach((exchangeBid) => { + const bidResponseBid = bidfactory.createBid(exchangeBid.ext.statusCode, exchangeBid) + + Object.assign( + bidResponseBid, + exchangeBid + ) + + if (exchangeBid.ext.renderer) { + const rendererParams = exchangeBid.ext.renderer + const renderHandler = createRenderHandler({ + bidResponseBid, + rendererConfig: rendererParams.config + }) + + bidResponseBid.renderer = Renderer.install( + Object.assign( + {}, + rendererParams, + { callback: () => renderHandler.onRendererLoad() } + ) + ) + bidResponseBid.renderer.setRender(() => renderHandler.render()) + } + + bidmanager.addBidResponse(exchangeBid.ext.placementCode, bidResponseBid) + }) + } catch (error) { + utils.logError(error); + bidRequestBids.forEach(bidRequestBid => { + const bidResponseBid = bidfactory.createBid(STATUS.NO_BID) + bidmanager.addBidResponse(bidRequestBid.placementCode, bidResponseBid) + }) + } + } + } +} + +function createUnrulyAdapter() { + const adapter = { + exchangeUrl: 'https://targeting.unrulymedia.com/prebid', + callBids({ bids: bidRequestBids }) { + if (!bidRequestBids || bidRequestBids.length === 0) { + return + } + + const payload = { + bidRequests: bidRequestBids + } + + const bidResponseHandler = createBidResponseHandler(bidRequestBids) + + ajax( + adapter.exchangeUrl, + bidResponseHandler.onBidResponse, + JSON.stringify(payload), + { + contentType: 'application/json', + withCredentials: true + } + ) + } + } + + return adapter +} + +module.exports = createUnrulyAdapter diff --git a/src/bidfactory.js b/src/bidfactory.js index 9b4da791311..74aa5b72a46 100644 --- a/src/bidfactory.js +++ b/src/bidfactory.js @@ -18,7 +18,7 @@ function Bid(statusCode, bidRequest) { var _bidId = bidRequest && bidRequest.bidId || utils.getUniqueIdentifierStr(); var _statusCode = statusCode || 0; - this.bidderCode = ''; + this.bidderCode = (bidRequest && bidRequest.bidder) || ''; this.width = 0; this.height = 0; this.statusMessage = _getStatus(); diff --git a/src/prebid.js b/src/prebid.js index 9041027f654..c0adadb3713 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -344,6 +344,7 @@ $$PREBID_GLOBAL$$.renderAd = function (doc, id) { iframe.height = height; iframe.width = width; iframe.style.display = 'inline'; + iframe.style.overflow = 'hidden'; iframe.src = url; utils.insertElement(iframe, doc, 'body'); diff --git a/test/spec/adapters/adform_spec.js b/test/spec/adapters/adform_spec.js index 80511be92ac..8888dbfe899 100644 --- a/test/spec/adapters/adform_spec.js +++ b/test/spec/adapters/adform_spec.js @@ -30,15 +30,16 @@ describe('Adform adapter', () => { assert.equal(_query.callback.split('.')[1], '_adf_callback'); assert.equal(_query.tid, 145); assert.equal(_query.rp, 4); + assert.equal(_query.fd, 1); assert.equal(_query.url, encodeURIComponent('some// there')); }); it('should correctly form bid items', () => { const _items = parseUrl(adLoader.loadScript.args[0][0]).items; - assert.deepEqual(_items[0], { mid: '1' }); - assert.deepEqual(_items[1], { mid: '2', someVar: 'someValue' }); - assert.deepEqual(_items[2], { mid: '3', pdom: 'home' }); + assert.deepEqual(_items[0], { mid: '1', transactionId: 'transactionId' }); + assert.deepEqual(_items[1], { mid: '2', someVar: 'someValue', transactionId: 'transactionId' }); + assert.deepEqual(_items[2], { mid: '3', pdom: 'home', transactionId: 'transactionId' }); }); }); @@ -60,6 +61,7 @@ describe('Adform adapter', () => { assert.equal(_bidObject.width, 90); assert.equal(_bidObject.height, 90); assert.equal(_bidObject.dealId, 'deal-1'); + assert.equal(_bidObject.transactionId, 'transactionId'); }); it('should correctly form empty bid response object', () => { @@ -111,6 +113,7 @@ describe('Adform adapter', () => { }); beforeEach(() => { + var transactionId = 'transactionId'; _adapter = adapter(); utils.getUniqueIdentifierStr = () => 'callback'; sandbox = sinon.sandbox.create(); @@ -126,7 +129,8 @@ describe('Adform adapter', () => { url: 'some// there' }, adxDomain: 'newdomain', - tid: 45 + tid: 45, + transactionId: transactionId }, { bidId: '123', @@ -136,7 +140,8 @@ describe('Adform adapter', () => { mid: 2, tid: 145, someVar: 'someValue' - } + }, + transactionId: transactionId }, { bidId: 'a1b', @@ -145,7 +150,8 @@ describe('Adform adapter', () => { params: { mid: 3, pdom: 'home' - } + }, + transactionId: transactionId } ]}); }); diff --git a/test/spec/adapters/admixer_spec.js b/test/spec/adapters/admixer_spec.js index 79174390b62..45f18ce7abc 100644 --- a/test/spec/adapters/admixer_spec.js +++ b/test/spec/adapters/admixer_spec.js @@ -39,6 +39,54 @@ describe('Admixer adapter', function () { } ] }; + var validVideoData_1 = { + bids: [ + { + mediaType: 'video', + bidder: 'admixer', + bidId: 'bid_id', + params: {zone: 'zone_id'}, + placementCode: 'ad-unit-1', + sizes: [[300, 250], [300, 600]] + } + ] + }; + var validVideoData_2 = { + bids: [ + { + mediaType: 'video', + bidder: 'admixer', + bidId: 'bid_id', + params: {zone: 'zone_id'}, + placementCode: 'ad-unit-1', + sizes: [300, 250] + } + ] + }; + var validVideoData_3 = { + bids: [ + { + mediaType: 'video', + bidder: 'admixer', + bidId: 'bid_id', + params: {zone: 'zone_id', video: {skippable: true}}, + placementCode: 'ad-unit-1', + sizes: [300, 250] + } + ] + }; + var invalidVideoData = { + bids: [ + { + mediaType: 'video', + bidder: 'admixer', + bidId: 'bid_id', + params: {}, + placementCode: 'ad-unit-1', + sizes: [[300, 250], [300, 600]] + } + ] + }; var responseWithAd = JSON.stringify({ 'result': { 'cpm': 2.2, @@ -57,13 +105,38 @@ describe('Admixer adapter', function () { }, 'callback_uid': 'ad-unit-1' }); + var responseWithVideoAd = JSON.stringify({ + 'result': { + 'cpm': 2.2, + 'vastUrl': 'http://inv-nets.admixer.net/vastxml.aspx?req=9d651544-daf4-48ed-ae0c-38a60a4e1920&vk=e914f026449e49aeb6eea07b9642a2ce', + 'width': 300, + 'height': 250 + }, + 'callback_uid': 'ad-unit-1' + }); + var responseWithoutVideoAd = JSON.stringify({ + 'result': { + 'cpm': 0, + 'vastUrl': '', + 'width': 0, + 'height': 0 + }, + 'callback_uid': 'ad-unit-1' + }); var responseEmpty = ''; var invUrl = '//inv-nets.admixer.net/prebid.aspx'; + var invVastUrl = '//inv-nets.admixer.net/videoprebid.aspx'; var validJsonParams = { zone: 'zone_id', callback_uid: 'ad-unit-1', sizes: '300x250-300x600' }; + var validJsonVideoParams = { + zone: 'zone_id', + callback_uid: 'ad-unit-1', + sizes: '300x250-300x600', + skippable: true + }; describe('bid request with valid data', function () { var stubAjax; beforeEach(function () { @@ -73,19 +146,32 @@ describe('Admixer adapter', function () { afterEach(function () { stubAjax.restore(); }); - it('bid request should be called. sizes style -> [[],[]]', function () { + it('display: bid request should be called. sizes style -> [[],[]]', function () { Adapter.callBids(validData_1); sinon.assert.calledOnce(stubAjax); }); - it('bid request should be called. sizes style -> []', function () { + it('video: bid request should be called. sizes style -> [[],[]]', function () { + Adapter.callBids(validVideoData_1); + sinon.assert.calledOnce(stubAjax); + }); + it('display: bid request should be called. sizes style -> []', function () { Adapter.callBids(validData_2); sinon.assert.calledOnce(stubAjax); }); - it('ajax params should be matched', function () { + it('video: bid request should be called. sizes style -> []', function () { + Adapter.callBids(validVideoData_2); + sinon.assert.calledOnce(stubAjax); + }); + it('display: ajax params should be matched', function () { Adapter.callBids(validData_1); sinon.assert.calledWith(stubAjax, sinon.match(invUrl, function () { }, validJsonParams, {method: 'GET'})); }); + it('video: ajax params should be matched', function () { + Adapter.callBids(validVideoData_3); + sinon.assert.calledWith(stubAjax, sinon.match(invVastUrl, function () { + }, validJsonVideoParams, {method: 'GET'})); + }); }); describe('bid request with invalid data', function () { var addBidResponse, stubAjax; @@ -98,15 +184,24 @@ describe('Admixer adapter', function () { addBidResponse.restore(); stubAjax.restore(); }); - it('ajax shouldn\'t be called', function () { + it('display: ajax shouldn\'t be called', function () { Adapter.callBids(invalidData); sinon.assert.notCalled(stubAjax); }); - it('bidmanager.addBidResponse status code must to be equal "' + CONSTANTS.STATUS.NO_BID + '"', function () { + it('video: ajax shouldn\'t be called', function () { + Adapter.callBids(invalidVideoData); + sinon.assert.notCalled(stubAjax); + }); + it('display: bidmanager.addBidResponse status code must to be equal "' + CONSTANTS.STATUS.NO_BID + '"', function () { Adapter.callBids(invalidData); expect(addBidResponse.firstCall.args[1].getStatusCode()).to.equal(CONSTANTS.STATUS.NO_BID); expect(addBidResponse.firstCall.args[1].bidderCode).to.equal('admixer'); }); + it('video: bidmanager.addBidResponse status code must to be equal "' + CONSTANTS.STATUS.NO_BID + '"', function () { + Adapter.callBids(invalidVideoData); + expect(addBidResponse.firstCall.args[1].getStatusCode()).to.equal(CONSTANTS.STATUS.NO_BID); + expect(addBidResponse.firstCall.args[1].bidderCode).to.equal('admixer'); + }); }); describe('bid response', function () { var addBidResponse; @@ -116,23 +211,35 @@ describe('Admixer adapter', function () { afterEach(function () { addBidResponse.restore(); }); - it('response with ad. bidmanager.addBidResponse status code must to be equal "' + CONSTANTS.STATUS.GOOD + '"', function () { + it('display: response with ad. bidmanager.addBidResponse status code must to be equal "' + CONSTANTS.STATUS.GOOD + '"', function () { Adapter.responseCallback(responseWithAd); var arg = addBidResponse.firstCall.args[1]; expect(arg.getStatusCode()).to.equal(CONSTANTS.STATUS.GOOD); expect(arg.bidderCode).to.equal('admixer'); }); - it('response without ad. bidmanager.addBidResponse status code must to be equal "' + CONSTANTS.STATUS.NO_BID, function () { + it('video: response with ad. bidmanager.addBidResponse status code must to be equal "' + CONSTANTS.STATUS.GOOD + '"', function () { + Adapter.responseCallback(responseWithVideoAd); + var arg = addBidResponse.firstCall.args[1]; + expect(arg.getStatusCode()).to.equal(CONSTANTS.STATUS.GOOD); + expect(arg.bidderCode).to.equal('admixer'); + }); + it('display: response without ad. bidmanager.addBidResponse status code must to be equal "' + CONSTANTS.STATUS.NO_BID, function () { Adapter.responseCallback(responseWithoutAd); var arg = addBidResponse.firstCall.args[1]; expect(arg.getStatusCode()).to.equal(CONSTANTS.STATUS.NO_BID); expect(arg.bidderCode).to.equal('admixer'); }); - it('response empty. bidmanager.addBidResponse status code must to be equal "' + CONSTANTS.STATUS.NO_BID, function () { + it('video: response without ad. bidmanager.addBidResponse status code must to be equal "' + CONSTANTS.STATUS.NO_BID, function () { + Adapter.responseCallback(responseWithoutVideoAd); + var arg = addBidResponse.firstCall.args[1]; + expect(arg.getStatusCode()).to.equal(CONSTANTS.STATUS.NO_BID); + expect(arg.bidderCode).to.equal('admixer'); + }); + it('display/video: response empty. bidmanager.addBidResponse status code must to be equal "' + CONSTANTS.STATUS.NO_BID, function () { Adapter.responseCallback(responseEmpty); var arg = addBidResponse.firstCall.args[1]; expect(arg.getStatusCode()).to.equal(CONSTANTS.STATUS.NO_BID); expect(arg.bidderCode).to.equal('admixer'); - }) + }); }); }); diff --git a/test/spec/adapters/adyoulike_spec.js b/test/spec/adapters/adyoulike_spec.js new file mode 100644 index 00000000000..d702b0ec283 --- /dev/null +++ b/test/spec/adapters/adyoulike_spec.js @@ -0,0 +1,308 @@ +import { expect } from 'chai'; +import { parse } from '../../../src/url'; +import AdyoulikAdapter from '../../../src/adapters/adyoulike'; +import bidmanager from 'src/bidmanager'; +import { STATUS } from 'src/constants'; + +describe('Adyoulike Adapter', () => { + const endpoint = 'http://hb-api.omnitagjs.com/hb-api/prebid'; + const canonicalUrl = 'http://canonical.url/?t=%26'; + const bidderCode = 'adyoulike'; + const bidRequestWithEmptyPlacement = { + 'bidderCode': 'adyoulike', + 'bids': [ + { + 'bidId': 'bid_id_0', + 'bidder': 'adyoulike', + 'placementCode': 'adunit/hb-0', + 'params': {}, + 'sizes': '300x250' + } + ], + }; + const bidRequestWithEmptySizes = { + 'bidderCode': 'adyoulike', + 'bids': [ + { + 'bidId': 'bid_id_0', + 'bidder': 'adyoulike', + 'placementCode': 'adunit/hb-0', + 'params': { + 'placement': 'placement_0' + } + } + ], + }; + const bidRequestWithSinglePlacement = { + 'bidderCode': 'adyoulike', + 'bids': [ + { + 'bidId': 'bid_id_0', + 'bidder': 'adyoulike', + 'placementCode': 'adunit/hb-0', + 'params': { + 'placement': 'placement_0' + }, + 'sizes': '300x250' + } + ], + }; + const bidRequestMultiPlacements = { + 'bidderCode': 'adyoulike', + 'bids': [ + { + 'bidId': 'bid_id_0', + 'bidder': 'adyoulike', + 'placementCode': 'adunit/hb-0', + 'params': { + 'placement': 'placement_0' + }, + 'sizes': '300x250' + }, + { + 'bidId': 'bid_id_1', + 'bidder': 'adyoulike', + 'placementCode': 'adunit/hb-1', + 'params': { + 'placement': 'placement_1' + }, + 'sizes': [[300, 600]] + }, + { + 'bidId': 'bid_id_2', + 'bidder': 'adyoulike', + 'placementCode': 'adunit/hb-2', + 'params': {}, + 'sizes': '300x400' + }, + { + 'bidId': 'bid_id_3', + 'bidder': 'adyoulike', + 'placementCode': 'adunit/hb-3', + 'params': { + 'placement': 'placement_3' + } + } + ], + }; + + const responseWithEmptyPlacement = [ + { + 'Placement': 'placement_0' + } + ]; + const responseWithSinglePlacement = [ + { + 'Placement': 'placement_0', + 'Banner': 'placement_0', + 'Price': 0.5 + } + ]; + const responseWithMultiplePlacements = [ + { + 'Placement': 'placement_0', + 'Banner': 'placement_0', + 'Price': 0.5 + }, + { + 'Placement': 'placement_1', + 'Banner': 'placement_1', + 'Price': 0.6 + } + ]; + + let adapter; + + beforeEach(() => { + adapter = new AdyoulikAdapter(); + }); + + describe('adapter public API', () => { + const adapter = AdyoulikAdapter.createNew(); + it('createNew', () => { + expect(adapter.createNew).to.be.a('function'); + }); + + it('setBidderCode', () => { + expect(adapter.setBidderCode).to.be.a('function'); + }); + it('callBids', () => { + expect(adapter.setBidderCode).to.be.a('function'); + }); + }); + + describe('request function', () => { + let requests; + let xhr; + let addBidResponse; + let canonicalQuery; + + beforeEach(() => { + requests = []; + + xhr = sinon.useFakeXMLHttpRequest(); + xhr.onCreate = request => requests.push(request); + + addBidResponse = sinon.stub(bidmanager, 'addBidResponse'); + + let canonical = document.createElement('link'); + canonical.rel = 'canonical'; + canonical.href = canonicalUrl; + canonicalQuery = sinon.stub(window.top.document.head, 'querySelector'); + canonicalQuery.withArgs('link[rel="canonical"][href]').returns(canonical); + }); + + afterEach(() => { + xhr.restore(); + bidmanager.addBidResponse.restore(); + canonicalQuery.restore(); + }); + + it('requires placement request', () => { + adapter.callBids(bidRequestWithEmptyPlacement); + expect(requests).to.be.empty; + expect(addBidResponse.calledOnce).to.equal(false); + }); + + it('requires sizes in request', () => { + adapter.callBids(bidRequestWithEmptySizes); + expect(requests).to.be.empty; + expect(addBidResponse.calledOnce).to.equal(false); + }); + + it('sends bid request to endpoint with single placement', () => { + adapter.callBids(bidRequestWithSinglePlacement); + expect(requests[0].url).to.contain(endpoint); + expect(requests[0].method).to.equal('POST'); + + expect(requests[0].url).to.contains('CanonicalUrl=' + encodeURIComponent(canonicalUrl)); + + let body = JSON.parse(requests[0].requestBody); + expect(body.Version).to.equal('0.1'); + expect(body.Placements).deep.equal(['placement_0']); + expect(body.PageRefreshed).to.equal(false); + }); + + it('sends bid request to endpoint with single placement without canonical', () => { + canonicalQuery.restore(); + + adapter.callBids(bidRequestWithSinglePlacement); + expect(requests[0].url).to.contain(endpoint); + expect(requests[0].method).to.equal('POST'); + + expect(requests[0].url).to.not.contains('CanonicalUrl=' + encodeURIComponent(canonicalUrl)); + + let body = JSON.parse(requests[0].requestBody); + expect(body.Version).to.equal('0.1'); + expect(body.Placements).deep.equal(['placement_0']); + expect(body.PageRefreshed).to.equal(false); + }); + + it('sends bid request to endpoint with multiple placements', () => { + adapter.callBids(bidRequestMultiPlacements); + expect(requests[0].url).to.contain(endpoint); + expect(requests[0].method).to.equal('POST'); + + expect(requests[0].url).to.contains('CanonicalUrl=' + encodeURIComponent(canonicalUrl)); + + let body = JSON.parse(requests[0].requestBody); + expect(body.Version).to.equal('0.1'); + expect(body.Placements).deep.equal(['placement_0', 'placement_1']); + expect(body.PageRefreshed).to.equal(false); + }); + }); + + describe('response function', () => { + let server; + let addBidResponse; + + beforeEach(() => { + server = sinon.fakeServer.create(); + addBidResponse = sinon.stub(bidmanager, 'addBidResponse'); + }); + + afterEach(() => { + server.restore(); + bidmanager.addBidResponse.restore(); + }); + + it('invalid json', () => { + server.respondWith('{'); + adapter.callBids(bidRequestWithSinglePlacement); + server.respond(); + + expect(addBidResponse.calledOnce).to.equal(true); + expect(addBidResponse.args[0]).to.have.lengthOf(2); + expect(addBidResponse.args[0][1].getStatusCode()).to.equal(STATUS.NO_BID); + expect(addBidResponse.args[0][1].bidderCode).to.equal(bidderCode); + }); + + it('receive reponse with empty placement', () => { + server.respondWith(JSON.stringify(responseWithEmptyPlacement)); + adapter.callBids(bidRequestWithSinglePlacement); + server.respond(); + + expect(addBidResponse.calledOnce).to.equal(true); + expect(addBidResponse.args[0]).to.have.lengthOf(2); + expect(addBidResponse.args[0][1].getStatusCode()).to.equal(STATUS.NO_BID); + expect(addBidResponse.args[0][1].bidderCode).to.equal(bidderCode); + }); + + it('receive reponse with single placement', () => { + server.respondWith(JSON.stringify(responseWithSinglePlacement)); + adapter.callBids(bidRequestWithSinglePlacement); + server.respond(); + + expect(addBidResponse.calledOnce).to.equal(true); + expect(addBidResponse.args[0]).to.have.lengthOf(2); + expect(addBidResponse.args[0][1].getStatusCode()).to.equal(STATUS.GOOD); + expect(addBidResponse.args[0][1].cpm).to.equal(0.5); + expect(addBidResponse.args[0][1].ad).to.equal('placement_0'); + expect(addBidResponse.args[0][1].width).to.equal(300); + expect(addBidResponse.args[0][1].height).to.equal(250); + }); + + it('receive reponse with multiple placement', () => { + server.respondWith(JSON.stringify(responseWithMultiplePlacements)); + adapter.callBids(bidRequestMultiPlacements); + server.respond(); + + expect(addBidResponse.calledTwice).to.equal(true); + + expect(addBidResponse.args[0]).to.have.lengthOf(2); + expect(addBidResponse.args[0][1].getStatusCode()).to.equal(STATUS.GOOD); + expect(addBidResponse.args[0][1].bidderCode).to.equal(bidderCode); + expect(addBidResponse.args[0][1].cpm).to.equal(0.5); + expect(addBidResponse.args[0][1].ad).to.equal('placement_0'); + expect(addBidResponse.args[0][1].width).to.equal(300); + expect(addBidResponse.args[0][1].height).to.equal(250); + + expect(addBidResponse.args[1]).to.have.lengthOf(2); + expect(addBidResponse.args[1][1].getStatusCode()).to.equal(STATUS.GOOD); + expect(addBidResponse.args[1][1].bidderCode).to.equal(bidderCode); + expect(addBidResponse.args[1][1].cpm).to.equal(0.6); + expect(addBidResponse.args[1][1].ad).to.equal('placement_1'); + expect(addBidResponse.args[1][1].width).to.equal(300); + expect(addBidResponse.args[1][1].height).to.equal(600); + }); + + it('receive reponse with invalid placement number', () => { + server.respondWith(JSON.stringify(responseWithSinglePlacement)); + adapter.callBids(bidRequestMultiPlacements); + server.respond(); + + expect(addBidResponse.calledTwice).to.equal(true); + + expect(addBidResponse.args[0]).to.have.lengthOf(2); + expect(addBidResponse.args[0][1].getStatusCode()).to.equal(STATUS.GOOD); + expect(addBidResponse.args[0][1].bidderCode).to.equal(bidderCode); + expect(addBidResponse.args[0][1].cpm).to.equal(0.5); + expect(addBidResponse.args[0][1].ad).to.equal('placement_0'); + expect(addBidResponse.args[0][1].width).to.equal(300); + expect(addBidResponse.args[0][1].height).to.equal(250); + + expect(addBidResponse.args[1]).to.have.lengthOf(2); + expect(addBidResponse.args[1][1].getStatusCode()).to.equal(STATUS.NO_BID); + }); + }); +}); diff --git a/test/spec/adapters/aol_spec.js b/test/spec/adapters/aol_spec.js index 3a13089295a..6f2a729e7e7 100644 --- a/test/spec/adapters/aol_spec.js +++ b/test/spec/adapters/aol_spec.js @@ -76,7 +76,7 @@ describe('AolAdapter', () => { beforeEach(() => adapter = new AolAdapter()); function createBidderRequest({bids, params} = {}) { - var bidderRequest = utils.cloneJson(getDefaultBidRequest()); + var bidderRequest = getDefaultBidRequest(); if (bids && Array.isArray(bids)) { bidderRequest.bids = bids; } @@ -568,8 +568,8 @@ describe('AolAdapter', () => { adapter.callBids(getDefaultBidRequest()); server.respond(); expect(bidmanager.addBidResponse.calledOnce).to.be.true; - var addedBidResponse = bidmanager.addBidResponse.firstCall.args[1]; - expect(addedBidResponse.ad).to.equal(""); + let addedBidResponse = bidmanager.addBidResponse.firstCall.args[1]; + expect(addedBidResponse.ad).to.equal(''); expect(addedBidResponse.cpm).to.equal(0.09); expect(addedBidResponse.width).to.equal(728); expect(addedBidResponse.height).to.equal(90); @@ -580,19 +580,19 @@ describe('AolAdapter', () => { it('should be added to bidmanager including pixels from pubapi response', () => { let bidResponse = getDefaultBidResponse(); bidResponse.ext = { - pixels: "" + pixels: '' }; server.respondWith(JSON.stringify(bidResponse)); adapter.callBids(getDefaultBidRequest()); server.respond(); expect(bidmanager.addBidResponse.calledOnce).to.be.true; - var addedBidResponse = bidmanager.addBidResponse.firstCall.args[1]; + let addedBidResponse = bidmanager.addBidResponse.firstCall.args[1]; expect(addedBidResponse.ad).to.equal( '' + '' + 'document.write(\'\');}' ); }); @@ -604,7 +604,7 @@ describe('AolAdapter', () => { adapter.callBids(getDefaultBidRequest()); server.respond(); expect(bidmanager.addBidResponse.calledOnce).to.be.true; - var addedBidResponse = bidmanager.addBidResponse.firstCall.args[1]; + let addedBidResponse = bidmanager.addBidResponse.firstCall.args[1]; expect(addedBidResponse.dealId).to.equal('12345'); }); @@ -623,7 +623,7 @@ describe('AolAdapter', () => { it('should not render pixels on pubapi response when no parameter is set', () => { let bidResponse = getDefaultBidResponse(); bidResponse.ext = { - pixels: "" + pixels: '' }; server.respondWith(JSON.stringify(bidResponse)); adapter.callBids(getDefaultBidRequest()); @@ -635,8 +635,8 @@ describe('AolAdapter', () => { it('should render pixels from pubapi response when param userSyncOn is set with \'bidResponse\'', () => { let bidResponse = getDefaultBidResponse(); bidResponse.ext = { - pixels: "" + pixels: '' }; server.respondWith(JSON.stringify(bidResponse)); @@ -664,8 +664,8 @@ describe('AolAdapter', () => { $$PREBID_GLOBAL$$.aolGlobals.pixelsDropped = true; let bidResponse = getDefaultBidResponse(); bidResponse.ext = { - pixels: "" + pixels: '' }; server.respondWith(JSON.stringify(bidResponse)); diff --git a/test/spec/adapters/carambola_spec.js b/test/spec/adapters/carambola_spec.js new file mode 100644 index 00000000000..fe856f66c21 --- /dev/null +++ b/test/spec/adapters/carambola_spec.js @@ -0,0 +1,139 @@ +import {expect} from 'chai'; +import * as utils from 'src/utils'; +import CarambolaAdapter from 'src/adapters/carambola'; +import bidmanager from 'src/bidmanager'; + +const DEFAULT_BIDDER_REQUEST = { + bidderCode: 'carambola', + requestId: 'c9ad932a-41d9-4821-b6dc-0c8146029faf', + adId: '2e3daacdeed03d', + start: new Date().getTime(), + bids: [{ + bidder: 'carambola', + adId: '2e3daacdeed03d', + requestId: 'c9ad932a-41d9-4821-b6dc-0c8146029faf', + adUnitCode: 'cbola_prebid_code_97', + token: 'CGYCLyIy', + pageViewId: '22478638', + params: { + pid: 'hbtest', + did: 112591, + wid: 0 + } + }] +}; + +const DEFAULT_HB_RESPONSE = { + cpm: 0.1693953107111156, + ad: ' ', + token: '9cd6bf9c-433d-4663-b67f-da727f4cebff', + width: '300', + height: '250', + currencyCode: 'USD', + pageViewId: '22478638', + requestStatus: 1 + +}; + +describe('carambolaAdapter', function () { + let adapter; + + beforeEach(() => adapter = new CarambolaAdapter()); + + function createBidderRequest({bids, params} = {}) { + var bidderRequest = utils.cloneJson(DEFAULT_BIDDER_REQUEST); + if (bids && Array.isArray(bids)) { + bidderRequest.bids = bids; + } + if (params) { + bidderRequest.bids.forEach(bid => bid.params = params); + } + return bidderRequest; + } + + describe('callBids()', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + + // bid request starts + describe('bid request', () => { + let xhr; + let requests; + + beforeEach(() => { + xhr = sinon.useFakeXMLHttpRequest(); + requests = []; + xhr.onCreate = request => requests.push(request); + }); + + afterEach(() => xhr.restore()); + + it('requires parameters to be made', () => { + adapter.callBids({}); + expect(requests[0]).to.be.empty; + }); + + it('should hit the default route.carambo.la endpoint', () => { + adapter.callBids(DEFAULT_BIDDER_REQUEST); + expect(requests[0].url).to.contain('route.carambo.la'); + }); + + it('should verifiy that a page_view_id is sent', () => { + adapter.callBids(DEFAULT_BIDDER_REQUEST); + expect(requests[0].url).to.contain('pageViewId='); + }); + + it('should should send the correct did', () => { + adapter.callBids(createBidderRequest({ + params: { + did: 112591, + wid: 0 + } + })); + expect(requests[0].url).to.contain('did=112591'); + }); + }); + // bid request ends + + // bid response starts + describe('bid response', () => { + let server; + + beforeEach(() => { + server = sinon.fakeServer.create(); + sinon.stub(bidmanager, 'addBidResponse'); + }); + + afterEach(() => { + server.restore(); + bidmanager.addBidResponse.restore(); + }); + + it('should be added to bidmanager if response is valid', () => { + server.respondWith(JSON.stringify(DEFAULT_HB_RESPONSE)); + adapter.callBids(DEFAULT_BIDDER_REQUEST); + server.respond(); + expect(bidmanager.addBidResponse.calledOnce).to.be.true; + }); + + it('should be added to bidmanager with correct bidderCode', () => { + server.respondWith(JSON.stringify(DEFAULT_HB_RESPONSE)); + adapter.callBids(DEFAULT_BIDDER_REQUEST); + server.respond(); + expect(bidmanager.addBidResponse.calledOnce).to.be.true; + expect(bidmanager.addBidResponse.firstCall.args[1]).to.have.property('bidderCode', 'carambola'); + }); + + it('should have pageViewId matching the pageViewId from related bid request', () => { + server.respondWith(JSON.stringify(DEFAULT_HB_RESPONSE)); + adapter.callBids(DEFAULT_BIDDER_REQUEST); + server.respond(); + expect(bidmanager.addBidResponse.calledOnce).to.be.true; + expect(bidmanager.addBidResponse.firstCall.args[1]) + .to.have.property('pvid', DEFAULT_BIDDER_REQUEST.bids[0].pageViewId); + }); + }); + // bid response ends + }); +}); diff --git a/test/spec/adapters/conversant_spec.js b/test/spec/adapters/conversant_spec.js index 3c9d636a700..09d5132683f 100644 --- a/test/spec/adapters/conversant_spec.js +++ b/test/spec/adapters/conversant_spec.js @@ -16,10 +16,11 @@ describe('Conversant adapter tests', function () { sizes: [[300, 600]], params: { site_id: '87293', + position: 1, + tag_id: 'tagid-1', secure: false } - }, - { + }, { bidId: 'bidId2', bidder: 'conversant', placementCode: 'div2', @@ -28,14 +29,27 @@ describe('Conversant adapter tests', function () { site_id: '87293', secure: false } - }, - { + }, { bidId: 'bidId3', bidder: 'conversant', placementCode: 'div3', sizes: [[300, 600], [160, 600]], params: { site_id: '87293', + position: 1, + tag_id: '', + secure: false + } + }, { + bidId: 'bidId4', + bidder: 'conversant', + placementCode: 'div4', + mediaType: 'video', + sizes: [[480, 480]], + params: { + site_id: '89192', + pos: 1, + tagid: 'tagid-4', secure: false } } @@ -101,7 +115,7 @@ describe('Conversant adapter tests', function () { expect(thirdBid.bidderCode).to.equal('conversant'); expect(placementCode3).to.equal('div3'); - expect(addBidResponseSpy.getCalls().length).to.equal(3); + expect(addBidResponseSpy.getCalls().length).to.equal(4); }); it('Should submit bids with statuses of 2 to the bid manager for empty bid responses', function () { @@ -126,7 +140,7 @@ describe('Conversant adapter tests', function () { expect(thirdBid.getStatusCode()).to.equal(2); expect(thirdBid.bidderCode).to.equal('conversant'); - expect(addBidResponseSpy.getCalls().length).to.equal(3); + expect(addBidResponseSpy.getCalls().length).to.equal(4); }); it('Should submit valid bids to the bid manager', function () { @@ -150,8 +164,7 @@ describe('Conversant adapter tests', function () { adm: 'adm2', h: 300, w: 600 - }, - { + }, { id: 33333, impid: 'bidId3', price: 0.33, @@ -190,8 +203,34 @@ describe('Conversant adapter tests', function () { expect(thirdBid.ad).to.equal('adm3' + ''); expect(placementCode3).to.equal('div3'); - expect(addBidResponseSpy.getCalls().length).to.equal(3); + expect(addBidResponseSpy.getCalls().length).to.equal(4); }); + + it('Should submit video bid responses correctly.', function () { + var bidResponse = { + id: 123, + seatbid: [{ + bid: [{ + id: 1111111, + impid: 'bidId4', + price: 0.11, + nurl: 'imp_tracker', + adm: 'vasturl' + }] + }] + }; + + $$PREBID_GLOBAL$$.conversantResponse(bidResponse); + + var videoBid = addBidResponseSpy.getCall(0).args[1]; + var placementCode = addBidResponseSpy.getCall(0).args[0]; + + expect(videoBid.getStatusCode()).to.equal(1); + expect(videoBid.bidderCode).to.equal('conversant'); + expect(videoBid.cpm).to.equal(0.11); + expect(videoBid.vastUrl).to.equal('vasturl'); + expect(placementCode).to.equal('div4'); + }) }); describe('Should submit the correct headers in the xhr', function () { @@ -218,8 +257,7 @@ describe('Conversant adapter tests', function () { adm: 'adm2', h: 300, w: 600 - }, - { + }, { id: 3333, impid: 'bidId3', price: 0.33, @@ -253,4 +291,86 @@ describe('Conversant adapter tests', function () { expect(request.requestBody).to.not.be.empty; }); }); + describe('Should create valid bid requests.', function () { + var server, + adapter; + + var bidResponse = { + id: 123, + seatbid: [{ + bid: [{ + id: 1111, + impid: 'bidId1', + price: 0.11, + nurl: '', + adm: 'adm', + h: 250, + w: 300, + ext: {} + }, { + id: 2222, + impid: 'bidId2', + price: 0.22, + nurl: '', + adm: 'adm2', + h: 300, + w: 600 + }, { + id: 3333, + impid: 'bidId3', + price: 0.33, + nurl: '', + adm: 'adm3', + h: 160, + w: 600 + }] + }] + }; + + beforeEach(function () { + server = sinon.fakeServer.create(); + adapter = new Adapter(); + }); + + afterEach(function () { + server.restore(); + }); + + beforeEach(function () { + var resp = [200, {'Content-type': 'text/javascript'}, '$$PREBID_GLOBAL$$.conversantResponse(\'' + JSON.stringify(bidResponse) + '\')']; + server.respondWith('POST', new RegExp('media.msg.dotomi.com/s2s/header'), resp); + }); + + it('Should create valid bid requests.', function () { + adapter.callBids(bidderRequest); + server.respond(); + var request = JSON.parse(server.requests[0].requestBody); + expect(request.imp[0].banner.format[0].w).to.equal(300); + expect(request.imp[0].banner.format[0].h).to.equal(600); + expect(request.imp[0].tagid).to.equal('tagid-1'); + expect(request.imp[0].banner.pos).to.equal(1); + expect(request.imp[0].secure).to.equal(0); + expect(request.site.id).to.equal('89192'); + }); + + it('Should not pass empty or missing optional parameters on requests.', function () { + adapter.callBids(bidderRequest); + server.respond(); + + var request = JSON.parse(server.requests[0].requestBody); + expect(request.imp[1].tagid).to.equal(undefined); + expect(request.imp[2].tagid).to.equal(undefined); + expect(request.imp[1].pos).to.equal(undefined); + }); + + it('Should create the format objects correctly.', function () { + adapter.callBids(bidderRequest); + server.respond(); + + var request = JSON.parse(server.requests[0].requestBody); + expect(request.imp[2].banner.format.length).to.equal(2); + expect(request.imp[2].banner.format[0].w).to.equal(300); + expect(request.imp[2].banner.format[1].w).to.equal(160); + }); + }); }); diff --git a/test/spec/adapters/cox_spec.js b/test/spec/adapters/cox_spec.js new file mode 100644 index 00000000000..3f1342230db --- /dev/null +++ b/test/spec/adapters/cox_spec.js @@ -0,0 +1,121 @@ +import Adapter from 'src/adapters/cox'; +import bidManager from 'src/bidmanager'; +import adLoader from 'src/adloader'; +import utils from 'src/utils'; +import {expect} from 'chai'; + +describe('CoxAdapter', () => { + let adapter; + let loadScriptStub; + let addBidResponseSpy; + + let emitScript = (script) => { + let node = document.createElement('script'); + node.type = 'text/javascript'; + node.appendChild(document.createTextNode(script)); + document.getElementsByTagName('head')[0].appendChild(node); + }; + + beforeEach(() => { + adapter = new Adapter(); + addBidResponseSpy = sinon.spy(bidManager, 'addBidResponse'); + }); + + afterEach(() => { + loadScriptStub.restore(); + addBidResponseSpy.restore(); + }); + + describe('response handling', () => { + const normalResponse = 'cdsTag.__callback__({"zones":{"as2000005991707":{"ad" : "

FOO<\/h1>","uid" : "","price" : 1.51,"floor" : 0,}},"tpCookieSync":"

FOOKIE<\/h1>"})'; + const zeroPriceResponse = 'cdsTag.__callback__({"zones":{"as2000005991707":{"ad" : "

DEFAULT FOO<\/h1>","uid" : "","price" : 0,"floor" : 0,}},"tpCookieSync":"

FOOKIE<\/h1>"})'; + const incompleteResponse = 'cdsTag.__callback__({"zones":{},"tpCookieSync":"

FOOKIE<\/h1>"})'; + + const oneBidConfig = { + bidderCode: 'cox', + bids: [{ + bidder: 'cox', + placementCode: 'FOO456789', + sizes: [300, 250], + params: { size: '300x250', id: 2000005991707, siteId: 2000100948180, env: 'PROD' }, + }] + }; + + // ===== 1 + it('should provide a correctly populated Bid given a valid response', () => { + loadScriptStub = sinon.stub(adLoader, 'loadScript', () => { emitScript(normalResponse); }) + + adapter.callBids(oneBidConfig); + + let bid = addBidResponseSpy.args[0][1]; + expect(bid.cpm).to.equal(1.51); + expect(bid.ad).to.be.a('string'); + expect(bid.bidderCode).to.equal('cox'); + }); + + // ===== 2 + it('should provide an empty Bid given a zero-price response', () => { + loadScriptStub = sinon.stub(adLoader, 'loadScript', () => { emitScript(zeroPriceResponse); }) + + adapter.callBids(oneBidConfig); + + let bid = addBidResponseSpy.args[0][1]; + expect(bid.cpm).to.not.be.ok + expect(bid.ad).to.not.be.ok; + }); + + // ===== 3 + it('should provide an empty Bid given an incomplete response', () => { + loadScriptStub = sinon.stub(adLoader, 'loadScript', () => { emitScript(incompleteResponse); }) + + adapter.callBids(oneBidConfig); + + let bid = addBidResponseSpy.args[0][1]; + expect(bid.cpm).to.not.be.ok + expect(bid.ad).to.not.be.ok; + }); + + // ===== 4 + it('should not provide a Bid given no response', () => { + loadScriptStub = sinon.stub(adLoader, 'loadScript', () => { emitScript(''); }); + + adapter.callBids(oneBidConfig); + + expect(addBidResponseSpy.callCount).to.equal(0); + }); + }); + + describe('request generation', () => { + const missingBidsConfig = { + bidderCode: 'cox', + bids: null, + }; + const missingParamsConfig = { + bidderCode: 'cox', + bids: [{ + bidder: 'cox', + placementCode: 'FOO456789', + sizes: [300, 250], + params: null, + }] + }; + + // ===== 5 + it('should not make an ad call given missing bids in config', () => { + loadScriptStub = sinon.stub(adLoader, 'loadScript'); + + adapter.callBids(missingBidsConfig); + + expect(loadScriptStub.callCount).to.equal(0); + }); + + // ===== 6 + it('should not make an ad call given missing params in config', () => { + loadScriptStub = sinon.stub(adLoader, 'loadScript'); + + adapter.callBids(missingParamsConfig); + + expect(loadScriptStub.callCount).to.equal(0); + }); + }); +}); diff --git a/test/spec/adapters/eplanning_spec.js b/test/spec/adapters/eplanning_spec.js new file mode 100644 index 00000000000..668392c169b --- /dev/null +++ b/test/spec/adapters/eplanning_spec.js @@ -0,0 +1,112 @@ +describe('eplanning adapter tests', function () { + var urlParse = require('url-parse'); + var querystringify = require('querystringify'); + var adapter = require('src/adapters/eplanning'); + var adLoader = require('src/adloader'); + var expect = require('chai').expect; + var bidmanager = require('src/bidmanager'); + var CONSTANTS = require('src/constants.json'); + + var DEFAULT_PARAMS = { + bidderCode: 'eplanning', + bids: [{ + code: 'div-gpt-ad-1460505748561-0', + sizes: [[300, 250], [300, 200]], + bidder: 'eplanning', + params: { + ci: '18f66' + } + }] + }; + + var PARAMS_SERVER_TEST = { + bidderCode: 'eplanning', + bids: [{ + code: 'div-gpt-ad-1460505748561-0', + sizes: [[300, 250], [300, 600]], + bidder: 'eplanning', + params: { + ci: '18f66', + t: '1' + } + }] + }; + + var RESPONSE_AD = { + bids: [{ + placementCode: 'div-gpt-ad-1460505748561-0', + ad: { + ad: '

test ad

', + cpm: 1, + width: 300, + height: 250 + } + }] + }; + + var RESPONSE_EMPTY = { + bids: [{ + placementCode: 'div-gpt-ad-1460505748561-0' + }] + }; + + var stubAddBidResponse; + + describe('eplanning tests', function() { + beforeEach(function() { + stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); + }); + afterEach(function() { + stubAddBidResponse.restore(); + }); + + it('callback function should exist', function() { + expect(pbjs.processEPlanningResponse).to.exist.and.to.be.a('function'); + }); + + it('creates a bid response if bid exists', function() { + adapter().callBids(DEFAULT_PARAMS); + pbjs.processEPlanningResponse(RESPONSE_AD); + + var bidPlacementCode = stubAddBidResponse.getCall(0).args[0]; + var bidObject = stubAddBidResponse.getCall(0).args[1]; + + expect(bidPlacementCode).to.equal('div-gpt-ad-1460505748561-0'); + expect(bidObject.cpm).to.equal(1); + expect(bidObject.ad).to.equal('

test ad

'); + expect(bidObject.width).to.equal(300); + expect(bidObject.height).to.equal(250); + expect(bidObject.getStatusCode()).to.equal(1); + expect(bidObject.bidderCode).to.equal('eplanning'); + }); + + it('creates an empty bid response if there is no bid', function() { + adapter().callBids(DEFAULT_PARAMS); + pbjs.processEPlanningResponse(RESPONSE_EMPTY); + + var bidPlacementCode = stubAddBidResponse.getCall(0).args[0]; + var bidObject = stubAddBidResponse.getCall(0).args[1]; + + expect(bidPlacementCode).to.equal('div-gpt-ad-1460505748561-0'); + expect(bidObject.getStatusCode()).to.equal(2); + expect(bidObject.bidderCode).to.equal('eplanning'); + }); + + it('creates a bid response and sync users register ad', function() { + adapter().callBids(DEFAULT_PARAMS); + window.hbpb.rH({ + 'sI': { 'k': '18f66' }, + 'sec': { 'k': 'ROS' }, + 'sp': [ { 'k': 'div-gpt-ad-1460505748561-0', 'a': [{ 'w': 300, 'h': 250, 'adm': '

test ad

', 'pr': 1 }] } ], + 'cs': [ + '//test.gif', + { 'j': true, u: '//test.js' }, + { 'ifr': true, u: '//test.html', data: { 'test': 1 } } + ] + }); + var bidPlacementCode = stubAddBidResponse.getCall(0).args[0]; + var bidObject = stubAddBidResponse.getCall(0).args[1]; + expect(bidObject.getStatusCode()).to.equal(2); + }); + }); +}); diff --git a/test/spec/adapters/gumgum_spec.js b/test/spec/adapters/gumgum_spec.js index 96e756d1052..a6d33dc5bd0 100644 --- a/test/spec/adapters/gumgum_spec.js +++ b/test/spec/adapters/gumgum_spec.js @@ -92,6 +92,74 @@ describe('gumgum adapter', () => { sandbox.restore(); }); + describe('DigiTrust params', () => { + beforeEach(() => { + sandbox.stub(adLoader, 'loadScript'); + }); + + it('should send digiTrust params', () => { + window.DigiTrust = { + getUser: function() {} + }; + sandbox.stub(window.DigiTrust, 'getUser', () => + ({ + success: true, + identity: { + privacy: {optout: false}, + id: 'testId' + } + }) + ); + + adapter.callBids(bidderRequest); + expect(adLoader.loadScript.firstCall.args[0]).to.include('&dt=testId'); + delete window.DigiTrust; + }); + + it('should not send DigiTrust params when DigiTrust is not loaded', () => { + adapter.callBids(bidderRequest); + expect(adLoader.loadScript.firstCall.args[0]).to.not.include('&dt'); + }); + + it('should not send DigiTrust params due to opt out', () => { + window.DigiTrust = { + getUser: function() {} + }; + sandbox.stub(window.DigiTrust, 'getUser', () => + ({ + success: true, + identity: { + privacy: {optout: true}, + id: 'testId' + } + }) + ); + + adapter.callBids(bidderRequest); + expect(adLoader.loadScript.firstCall.args[0]).to.not.include('&dt'); + delete window.DigiTrust; + }); + + it('should not send DigiTrust params on failure', () => { + window.DigiTrust = { + getUser: function() {} + }; + sandbox.stub(window.DigiTrust, 'getUser', () => + ({ + success: false, + identity: { + privacy: {optout: false}, + id: 'testId' + } + }) + ); + + adapter.callBids(bidderRequest); + expect(adLoader.loadScript.firstCall.args[0]).to.not.include('&dt'); + delete window.DigiTrust; + }); + }); + describe('callBids', () => { beforeEach(() => { sandbox.stub(adLoader, 'loadScript'); diff --git a/test/spec/adapters/prebidServer_spec.js b/test/spec/adapters/prebidServer_spec.js index 0d047133c97..fe1cb7e9894 100644 --- a/test/spec/adapters/prebidServer_spec.js +++ b/test/spec/adapters/prebidServer_spec.js @@ -66,7 +66,8 @@ const RESPONSE = { 'price': 0.5, 'adm': '', 'width': 300, - 'height': 250 + 'height': 250, + 'deal_id': 'test-dealid' } ] }; @@ -143,16 +144,20 @@ describe('S2S Adapter', () => { sinon.stub(bidmanager, 'addBidResponse'); sinon.stub(utils, 'getBidderRequestAllAdUnits').returns({ bids: [{ - bidId: '32167', + bidId: '123', placementCode: 'div-gpt-ad-1460505748561-0' }] }); + sinon.stub(utils, 'getBidRequest').returns({ + bidId: '123' + }); }); afterEach(() => { server.restore(); bidmanager.addBidResponse.restore(); utils.getBidderRequestAllAdUnits.restore(); + utils.getBidRequest.restore(); }); it('registers bids', () => { @@ -166,9 +171,10 @@ describe('S2S Adapter', () => { const response = bidmanager.addBidResponse.firstCall.args[1]; expect(response).to.have.property('statusMessage', 'Bid available'); expect(response).to.have.property('cpm', 0.5); + expect(response).to.have.property('adId', '123'); }); - it('registers no bid response when ad unit not set', () => { + it('registers no-bid response when ad unit not set', () => { server.respondWith(JSON.stringify(RESPONSE_NO_BID_NO_UNIT)); adapter.setConfig(CONFIG); @@ -183,10 +189,10 @@ describe('S2S Adapter', () => { expect(response).to.have.property('statusMessage', 'Bid returned empty or error response'); const bid_request_passed = bidmanager.addBidResponse.firstCall.args[1]; - expect(bid_request_passed).to.have.property('adId', '32167'); + expect(bid_request_passed).to.have.property('adId', '123'); }); - it('registers no bid response when server requests cookie sync', () => { + it('registers no-bid response when server requests cookie sync', () => { server.respondWith(JSON.stringify(RESPONSE_NO_COOKIE)); adapter.setConfig(CONFIG); @@ -201,10 +207,10 @@ describe('S2S Adapter', () => { expect(response).to.have.property('statusMessage', 'Bid returned empty or error response'); const bid_request_passed = bidmanager.addBidResponse.firstCall.args[1]; - expect(bid_request_passed).to.have.property('adId', '32167'); + expect(bid_request_passed).to.have.property('adId', '123'); }); - it('registers no bid response when ad unit is set', () => { + it('registers no-bid response when ad unit is set', () => { server.respondWith(JSON.stringify(RESPONSE_NO_BID_UNIT_SET)); adapter.setConfig(CONFIG); @@ -218,5 +224,47 @@ describe('S2S Adapter', () => { const response = bidmanager.addBidResponse.firstCall.args[1]; expect(response).to.have.property('statusMessage', 'Bid returned empty or error response'); }); + + it('registers no-bid response when there are less bids than requests', () => { + utils.getBidderRequestAllAdUnits.restore(); + sinon.stub(utils, 'getBidderRequestAllAdUnits').returns({ + bids: [{ + bidId: '123', + placementCode: 'div-gpt-ad-1460505748561-0' + }, { + bidId: '101111', + placementCode: 'div-gpt-ad-1460505748561-1' + }] + }); + + server.respondWith(JSON.stringify(RESPONSE)); + + adapter.setConfig(CONFIG); + adapter.callBids(REQUEST); + server.respond(); + + sinon.assert.calledTwice(bidmanager.addBidResponse); + + expect(bidmanager.addBidResponse.firstCall.args[0]).to.equal('div-gpt-ad-1460505748561-0'); + expect(bidmanager.addBidResponse.secondCall.args[0]).to.equal('div-gpt-ad-1460505748561-1'); + + expect(bidmanager.addBidResponse.firstCall.args[1]).to.have.property('adId', '123'); + expect(bidmanager.addBidResponse.secondCall.args[1]).to.have.property('adId', '101111'); + + expect(bidmanager.addBidResponse.firstCall.args[1]) + .to.have.property('statusMessage', 'Bid available'); + expect(bidmanager.addBidResponse.secondCall.args[1]) + .to.have.property('statusMessage', 'Bid returned empty or error response'); + }); + + it('should have dealId in bidObject', () => { + server.respondWith(JSON.stringify(RESPONSE)); + + adapter.setConfig(CONFIG); + adapter.callBids(REQUEST); + server.respond(); + const response = bidmanager.addBidResponse.firstCall.args[1]; + expect(response).to.have.property('dealId', 'test-dealid'); + }); }); }); diff --git a/test/spec/adapters/trion_spec.js b/test/spec/adapters/trion_spec.js index b5e79cf7312..dbbcf66ec87 100644 --- a/test/spec/adapters/trion_spec.js +++ b/test/spec/adapters/trion_spec.js @@ -1,6 +1,7 @@ import { expect } from 'chai'; import trionAdapter from 'src/adapters/trion'; import bidmanager from 'src/bidmanager'; +import * as utils from 'src/utils'; const CONSTANTS = require('src/constants.json'); const adloader = require('src/adloader'); @@ -106,7 +107,8 @@ describe('Trion adapter tests', () => { let bidUrl = spyLoadScript.getCall(0).args[0]; expect(bidUrl).to.include('re=1'); - expect(bidUrl).to.include(window.location.href); + expect(bidUrl).to.include(utils.getTopWindowUrl()); + expect(bidUrl).to.include('slot=' + PLACEMENT_CODE); delete params.re; }); diff --git a/test/spec/adapters/unruly_spec.js b/test/spec/adapters/unruly_spec.js new file mode 100644 index 00000000000..de0aa683bc9 --- /dev/null +++ b/test/spec/adapters/unruly_spec.js @@ -0,0 +1,278 @@ +/* globals describe, it, beforeEach, afterEach, sinon */ +import { expect } from 'chai' +import bidfactory from 'src/bidfactory' +import bidmanager from 'src/bidmanager' +import * as utils from 'src/utils' +import { STATUS } from 'src/constants' +import { Renderer } from 'src/Renderer' +import createUnrulyAdapter from 'src/adapters/unruly' + +describe('UnrulyAdapter', () => { + function createBidRequestBid({ placementCode }) { + return { + 'bidder': 'unruly', + 'params': { + 'uuid': '74544e00-d43b-4f3a-a799-69d22ce979ce', + 'siteId': 794599, + 'placementId': '5768085' + }, + 'placementCode': placementCode, + 'mediaType': 'video', + 'transactionId': '62890707-3770-497c-a3b8-d905a2d0cb98', + 'sizes': [ + 640, + 480 + ], + 'bidId': '23b86d8f6335ce', + 'bidderRequestId': '1d5b7474eb5416', + 'requestId': '406fe12b-fa3b-4bd3-b3c8-043951b4dac1' + } + } + + function createParams(...bids) { + return { + 'bidderCode': 'unruly', + 'requestId': '406fe12b-fa3b-4bd3-b3c8-043951b4dac1', + 'bidderRequestId': '1d5b7474eb5416', + 'bids': bids, + 'start': 1495794517251, + 'auctionStart': 1495794517250, + 'timeout': 3000 + } + } + + function createOutStreamExchangeBid({ placementCode, statusCode = 1 }) { + return { + 'ext': { + 'statusCode': statusCode, + 'renderer': { + 'id': 'unruly_inarticle', + 'config': {}, + 'url': 'https://video.unrulymedia.com/native/prebid-loader.js' + }, + 'placementCode': placementCode + }, + 'cpm': 20, + 'bidderCode': 'unruly', + 'width': 323, + 'vastUrl': 'https://targeting.unrulymedia.com/in_article?uuid=74544e00-d43b-4f3a-a799-69d22ce979ce&supported_mime_type=application/javascript&supported_mime_type=video/mp4&tj=%7B%22site%22%3A%7B%22lang%22%3A%22en-GB%22%2C%22ref%22%3A%22%22%2C%22page%22%3A%22http%3A%2F%2Fdemo.unrulymedia.com%2FinArticle%2Finarticle_nypost_upbeat%2Ftravel_magazines.html%22%2C%22domain%22%3A%22demo.unrulymedia.com%22%7D%2C%22user%22%3A%7B%22profile%22%3A%7B%22quantcast%22%3A%7B%22segments%22%3A%5B%7B%22id%22%3A%22D%22%7D%2C%7B%22id%22%3A%22T%22%7D%5D%7D%7D%7D%7D&video_width=618&video_height=347', + 'bidId': 'foo', + 'height': 323 + } + } + + function createInStreamExchangeBid({ placementCode, statusCode = 1 }) { + return { + 'ext': { + 'statusCode': statusCode, + 'placementCode': placementCode + }, + 'cpm': 20, + 'bidderCode': 'unruly', + 'width': 323, + 'vastUrl': 'https://targeting.unrulymedia.com/in_article?uuid=74544e00-d43b-4f3a-a799-69d22ce979ce&supported_mime_type=application/javascript&supported_mime_type=video/mp4&tj=%7B%22site%22%3A%7B%22lang%22%3A%22en-GB%22%2C%22ref%22%3A%22%22%2C%22page%22%3A%22http%3A%2F%2Fdemo.unrulymedia.com%2FinArticle%2Finarticle_nypost_upbeat%2Ftravel_magazines.html%22%2C%22domain%22%3A%22demo.unrulymedia.com%22%7D%2C%22user%22%3A%7B%22profile%22%3A%7B%22quantcast%22%3A%7B%22segments%22%3A%5B%7B%22id%22%3A%22D%22%7D%2C%7B%22id%22%3A%22T%22%7D%5D%7D%7D%7D%7D&video_width=618&video_height=347', + 'bidId': 'foo', + 'height': 323 + } + } + + function createExchangeResponse(...bids) { + return { + 'bids': bids + } + } + + let adapter + let server + let sandbox + let fakeRenderer + + beforeEach(() => { + adapter = createUnrulyAdapter() + adapter.exchangeUrl = 'http://localhost:9000/prebid' + + sandbox = sinon.sandbox.create() + sandbox.stub(bidmanager, 'addBidResponse') + sandbox.stub(bidfactory, 'createBid') + sandbox.stub(utils, 'logError') + + fakeRenderer = { + setRender: sinon.stub() + } + + sandbox.stub(Renderer, 'install') + Renderer.install.returns(fakeRenderer) + + server = sinon.fakeServer.create() + }) + + afterEach(() => { + sandbox.restore() + server.restore() + delete parent.window.unruly + }) + + describe('callBids', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function') + }) + + it('requires bids to make request', () => { + adapter.callBids({}) + expect(server.requests).to.be.empty + }) + + it('requires at least one bid to make request', () => { + adapter.callBids({ bids: [] }) + expect(server.requests).to.be.empty + }) + + it('passes bids through to exchange', () => { + const params = createParams(createBidRequestBid({ placementCode: 'placement1' })) + + adapter.callBids(params) + + expect(server.requests).to.have.length(1) + expect(server.requests[0].url).to.equal('http://localhost:9000/prebid') + + const requestBody = JSON.parse(server.requests[0].requestBody) + expect(requestBody).to.deep.equal({ + 'bidRequests': params.bids + }) + }) + + it('creates a bid response using status code from exchange for each bid and passes in the exchange response', () => { + const params = createParams(createBidRequestBid({ placementCode: 'placement1' })) + + const exchangeBid1 = createOutStreamExchangeBid({ placementCode: 'placement1' }) + const exchangeBid2 = createOutStreamExchangeBid({ placementCode: 'placement2', statusCode: 2 }) + const exchangeResponse = createExchangeResponse(exchangeBid1, exchangeBid2) + + server.respondWith(JSON.stringify(exchangeResponse)) + bidfactory.createBid.returns({}) + + adapter.callBids(params) + server.respond() + + sinon.assert.calledTwice(bidfactory.createBid) + sinon.assert.calledWith(bidfactory.createBid, exchangeBid1.ext.statusCode, exchangeResponse.bids[0]) + sinon.assert.calledWith(bidfactory.createBid, exchangeBid2.ext.statusCode, exchangeResponse.bids[1]) + }) + + it('adds the bid response to the bid manager', () => { + const fakeBid = {} + + const params = createParams(createBidRequestBid({ placementCode: 'placement1' })) + const exchangeBid = createOutStreamExchangeBid({ placementCode: 'placement1' }) + const exchangeResponse = createExchangeResponse(exchangeBid) + + server.respondWith(JSON.stringify(exchangeResponse)) + bidfactory.createBid.withArgs(exchangeBid.ext.statusCode).returns(fakeBid) + + adapter.callBids(params) + server.respond() + + sinon.assert.calledOnce(bidmanager.addBidResponse) + sinon.assert.calledWith(bidmanager.addBidResponse, exchangeBid.ext.placementCode, fakeBid) + }) + + describe('on invalid exchange response', () => { + it('should create NO_BID response for each bid request bid', () => { + const bidRequestBid1 = createBidRequestBid({ placementCode: 'placement1' }) + const bidRequestBid2 = createBidRequestBid({ placementCode: 'placement2' }) + const params = createParams(bidRequestBid1, bidRequestBid2) + const expectedBid = { 'some': 'props' } + + server.respondWith('this is not json') + bidfactory.createBid.withArgs(STATUS.NO_BID).returns(expectedBid) + + adapter.callBids(params) + server.respond() + + sinon.assert.calledOnce(utils.logError) + sinon.assert.calledTwice(bidmanager.addBidResponse) + sinon.assert.calledWith(bidmanager.addBidResponse, bidRequestBid1.placementCode, expectedBid) + sinon.assert.calledWith(bidmanager.addBidResponse, bidRequestBid2.placementCode, expectedBid) + }) + }) + + describe('InStream', () => { + it('merges bid response defaults', () => { + const params = createParams(createBidRequestBid({ placementCode: 'placement1' })) + + const fakeBidDefaults = { some: 'default' } + const fakeBid = Object.assign({}, fakeBidDefaults) + + const exchangeBid = createInStreamExchangeBid({ placementCode: 'placement1' }) + const exchangeResponse = createExchangeResponse(exchangeBid) + server.respondWith(JSON.stringify(exchangeResponse)) + + bidfactory.createBid.withArgs(exchangeBid.ext.statusCode).returns(fakeBid) + + adapter.callBids(params) + server.respond() + + sinon.assert.notCalled(Renderer.install) + expect(fakeBid).to.deep.equal(Object.assign( + {}, + fakeBidDefaults, + exchangeBid + )) + }) + }) + + describe('OutStream', () => { + it('merges bid response defaults with exchange bid and renderer', () => { + const params = createParams(createBidRequestBid({ placementCode: 'placement1' })) + + const fakeBidDefaults = { some: 'default' } + const fakeBid = Object.assign({}, fakeBidDefaults) + + const exchangeBid = createOutStreamExchangeBid({ placementCode: 'placement1' }) + const exchangeResponse = createExchangeResponse(exchangeBid) + server.respondWith(JSON.stringify(exchangeResponse)) + + bidfactory.createBid.withArgs(exchangeBid.ext.statusCode).returns(fakeBid) + + const fakeRenderer = {} + Renderer.install.withArgs(Object.assign( + {}, + exchangeBid.ext.renderer, + { callback: sinon.match.func } + )).returns(fakeRenderer) + + adapter.callBids(params) + server.respond() + + expect(fakeBid).to.deep.equal(Object.assign( + {}, + fakeBidDefaults, + exchangeBid, + { renderer: fakeRenderer } + )) + }) + + it('bid is placed on the bid queue when render is called', () => { + const params = createParams(createBidRequestBid({ placementCode: 'placement1' })) + + const fakeBidDefaults = { some: 'default' } + const fakeBid = Object.assign({}, fakeBidDefaults) + + const exchangeBid = createOutStreamExchangeBid({ placementCode: 'placement1' }) + const exchangeResponse = createExchangeResponse(exchangeBid) + server.respondWith(JSON.stringify(exchangeResponse)) + + bidfactory.createBid.withArgs(exchangeBid.ext.statusCode).returns(fakeBid) + + adapter.callBids(params) + server.respond() + + sinon.assert.calledOnce(fakeRenderer.setRender) + fakeRenderer.setRender.firstCall.args[0]() + + expect(window.top).to.have.deep.property('unruly.native.prebid.uq'); + expect(window.top.unruly.native.prebid.uq).to.deep.equal([['render', fakeBid]]) + }) + }) + }) +}) diff --git a/test/spec/renderer_spec.js b/test/spec/renderer_spec.js index ba60923ed2a..282c4841ac0 100644 --- a/test/spec/renderer_spec.js +++ b/test/spec/renderer_spec.js @@ -53,4 +53,39 @@ describe('Renderer: A renderer installed on a bid response', () => { testRenderer1.handleVideoEvent({ id: 1, eventName: 'testEvent' }); expect(spyEventHandler.called).to.equal(true); }); + + it('pushes commands to queue if renderer is not loaded', () => { + testRenderer1.push(spyRenderFn); + + expect(testRenderer1.cmd.length).to.equal(1); + + // clear queue for next tests + testRenderer1.cmd = []; + }); + + it('fires commands immediately if the renderer is loaded', () => { + const func = sinon.spy(); + + testRenderer1.loaded = true; + testRenderer1.push(func); + + expect(testRenderer1.cmd.length).to.equal(0); + sinon.assert.calledOnce(func); + }); + + it('processes queue by calling each function in queue', () => { + testRenderer1.loaded = false; + const func1 = sinon.spy(); + const func2 = sinon.spy(); + + testRenderer1.push(func1); + testRenderer1.push(func2); + expect(testRenderer1.cmd.length).to.equal(2); + + testRenderer1.process(); + + sinon.assert.calledOnce(func1); + sinon.assert.calledOnce(func2); + expect(testRenderer1.cmd.length).to.equal(0); + }); });