diff --git a/PR_REVIEW.md b/PR_REVIEW.md
index 2a870d9e2f6b..012a2d8b501d 100644
--- a/PR_REVIEW.md
+++ b/PR_REVIEW.md
@@ -34,6 +34,7 @@ For modules and core platform updates, the initial reviewer should request an ad
- Adapters may not use the $$PREBID_GLOBAL$$ variable
- All adapters must support the creation of multiple concurrent instances. This means, for example, that adapters cannot rely on mutable global variables.
- Adapters may not globally override or default the standard ad server targeting values: hb_adid, hb_bidder, hb_pb, hb_deal, or hb_size, hb_source, hb_format.
+- After a new adapter is approved, let the submitter know they may open a PR in the [headerbid-expert repository](https://github.com/prebid/headerbid-expert) to have their adapter recognized by the [Headerbid Expert extension](https://chrome.google.com/webstore/detail/headerbid-expert/cgfkddgbnfplidghapbbnngaogeldmop). The PR should be to the [bidder patterns file](https://github.com/prebid/headerbid-expert/blob/master/bidderPatterns.js), adding an entry with their adapter's name and the url the adapter uses to send and receive bid responses.
## Ticket Coordinator
diff --git a/README.md b/README.md
index 1ec83859b48d..22522ffe55af 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
[](https://coveralls.io/github/prebid/Prebid.js)
[](https://david-dm.org/prebid/Prebid.js?type=dev)
-# Prebid.js
+# Prebid.js 1.9
> A free and open source library for publishers to quickly implement header bidding.
diff --git a/integrationExamples/gpt/gdpr_hello_world.html b/integrationExamples/gpt/gdpr_hello_world.html
new file mode 100644
index 000000000000..9f6194edb16f
--- /dev/null
+++ b/integrationExamples/gpt/gdpr_hello_world.html
@@ -0,0 +1,103 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Prebid.js Test
+ Div-1
+
+
+
+
+
\ No newline at end of file
diff --git a/modules/adformBidAdapter.js b/modules/adformBidAdapter.js
index 1da6042f63ae..a84def819c19 100644
--- a/modules/adformBidAdapter.js
+++ b/modules/adformBidAdapter.js
@@ -10,16 +10,15 @@ export const spec = {
isBidRequestValid: function (bid) {
return !!(bid.params.mid);
},
- buildRequests: function (validBidRequests) {
- var i, l, j, k, bid, _key, _value, reqParams;
+ buildRequests: function (validBidRequests, bidderRequest) {
+ var i, l, j, k, bid, _key, _value, reqParams, netRevenue;
var request = [];
- var globalParams = [ [ 'adxDomain', 'adx.adform.net' ], [ 'fd', 1 ], [ 'url', null ], [ 'tid', null ], [ 'pt', null ] ];
- var netRevenue = 'gross';
+ var globalParams = [ [ 'adxDomain', 'adx.adform.net' ], [ 'fd', 1 ], [ 'url', null ], [ 'tid', null ] ];
var bids = JSON.parse(JSON.stringify(validBidRequests));
for (i = 0, l = bids.length; i < l; i++) {
bid = bids[i];
- if (bid.params.priceType === 'net') {
- bid.params.pt = netRevenue = 'net';
+ if ((bid.params.priceType === 'net') || (bid.params.pt === 'net')) {
+ netRevenue = 'net';
}
for (j = 0, k = globalParams.length; j < k; j++) {
_key = globalParams[j][0];
@@ -35,9 +34,15 @@ export const spec = {
}
request.unshift('//' + globalParams[0][1] + '/adx/?rp=4');
-
+ netRevenue = netRevenue || 'gross';
+ request.push('pt=' + netRevenue);
request.push('stid=' + validBidRequests[0].auctionId);
+ if (bidderRequest && bidderRequest.gdprConsent) {
+ request.push('gdpr=' + bidderRequest.gdprConsent.gdprApplies);
+ request.push('gdpr_consent=' + bidderRequest.gdprConsent.consentString);
+ }
+
for (i = 1, l = globalParams.length; i < l; i++) {
_key = globalParams[i][0];
_value = globalParams[i][1];
diff --git a/modules/admaticBidAdapter.js b/modules/admaticBidAdapter.js
new file mode 100644
index 000000000000..28858aceaa1f
--- /dev/null
+++ b/modules/admaticBidAdapter.js
@@ -0,0 +1,147 @@
+import * as utils from 'src/utils';
+import { registerBidder } from 'src/adapters/bidderFactory';
+
+const BIDDER_CODE = 'admatic';
+const ENDPOINT_URL = '//ads4.admatic.com.tr/prebid/v3/bidrequest';
+
+export const spec = {
+ code: BIDDER_CODE,
+ aliases: ['admatic'], // short code
+ /**
+ * Determines whether or not the given bid request is valid.
+ *
+ * @param {BidRequest} bid The bid params to validate.
+ * @return boolean True if this is a valid bid, and false otherwise.
+ */
+ isBidRequestValid: function (bid) {
+ return !!(bid.params.pid && bid.params.wid && bid.params.url);
+ },
+ /**
+ * Make a server request from the list of BidRequests.
+ *
+ * @param {validBidRequests[]} - an array of bids
+ * @return ServerRequest Info describing the request to the server.
+ */
+ buildRequests: function (validBidRequests) {
+ const payload = {
+ request: []
+ };
+
+ for (var i = 0; i < validBidRequests.length; i++) {
+ var validBidRequest = validBidRequests[i];
+ payload.auctionId = validBidRequest.auctionId;
+ payload.bidder = validBidRequest.bidder;
+ payload.bidderRequestId = validBidRequest.bidderRequestId;
+ payload.pid = validBidRequest.params.pid;
+ payload.wid = validBidRequest.params.wid;
+ payload.url = validBidRequest.params.url;
+
+ var request = {
+ adUnitCode: validBidRequest.adUnitCode,
+ bidId: validBidRequest.bidId,
+ transactionId: validBidRequest.transactionId,
+ priceType: validBidRequest.params.priceType,
+ sizes: transformSizes(validBidRequest.sizes)
+ }
+
+ payload.request.push(request);
+ }
+
+ const payloadString = JSON.stringify(payload);
+
+ return {
+ method: 'POST',
+ url: ENDPOINT_URL,
+ data: payloadString,
+ bidder: 'admatic',
+ bids: validBidRequests
+ };
+ },
+
+ /**
+ * Unpack the response from the server into a list of bids.
+ *
+ * @param {ServerResponse} serverResponse A successful response from the server.
+ * @return {Bid[]} An array of bids which were nested inside the server.
+ */
+ interpretResponse: function (serverResponse, bidRequest) {
+ const serverBody = serverResponse.body;
+ const bidResponses = [];
+
+ if (serverBody) {
+ if (serverBody.tags && serverBody.tags.length > 0) {
+ serverBody.tags.forEach(serverBid => {
+ if (serverBid != null) {
+ if (serverBid.cpm !== 0) {
+ const bidResponse = {
+ requestId: serverBid.bidId,
+ cpm: serverBid.cpm,
+ width: serverBid.width,
+ height: serverBid.height,
+ creativeId: serverBid.creativeId,
+ dealId: serverBid.dealId,
+ currency: serverBid.currency,
+ netRevenue: serverBid.netRevenue,
+ ttl: serverBid.ttl,
+ referrer: serverBid.referrer,
+ ad: serverBid.ad
+ };
+
+ bidResponses.push(bidResponse);
+ }
+ }
+ });
+ }
+ }
+
+ return bidResponses;
+ },
+ /**
+ * Register the user sync pixels which should be dropped after the auction.
+ *
+ * @param {SyncOptions} syncOptions Which user syncs are allowed?
+ * @param {ServerResponse[]} serverResponses List of server's responses.
+ * @return {UserSync[]} The user syncs which should be dropped.
+ */
+ getUserSyncs: function (syncOptions, serverResponses) {
+ const syncs = [];
+ if (syncOptions.iframeEnabled) {
+ syncs.push({
+ type: 'iframe',
+ url: '//ads4.admatic.com.tr/prebid/static/usersync/v3/async_usersync.html'
+ });
+ }
+
+ if (syncOptions.pixelEnabled && serverResponses.length > 0) {
+ syncs.push({
+ type: 'image',
+ url: 'https://ads5.admatic.com.tr/prebid/v3/bidrequest/usersync'
+ });
+ }
+ return syncs;
+ }
+}
+
+/* Turn bid request sizes into ut-compatible format */
+function transformSizes(requestSizes) {
+ let sizes = [];
+ let sizeObj = {};
+
+ if (utils.isArray(requestSizes) && requestSizes.length === 2 && !utils.isArray(requestSizes[0])) {
+ sizeObj.width = parseInt(requestSizes[0], 10);
+ sizeObj.height = parseInt(requestSizes[1], 10);
+ sizes.push(sizeObj);
+ } else if (typeof requestSizes === 'object') {
+ for (let i = 0; i < requestSizes.length; i++) {
+ let size = requestSizes[i];
+ sizeObj = {};
+ sizeObj.width = parseInt(size[0], 10);
+ sizeObj.height = parseInt(size[1], 10);
+ sizes.push(sizeObj);
+ }
+ }
+
+ return sizes;
+}
+
+registerBidder(spec);
diff --git a/modules/admaticBidAdapter.md b/modules/admaticBidAdapter.md
new file mode 100644
index 000000000000..f6e822b9c06c
--- /dev/null
+++ b/modules/admaticBidAdapter.md
@@ -0,0 +1,54 @@
+# Overview
+
+```
+Module Name: AdMatic Bidder Adapter
+Module Type: Bidder Adapter
+Maintainer: prebid@admatic.com.tr
+```
+
+# Description
+
+Module that connects to AdMatic demand sources
+
+# Test Parameters
+```
+ var adUnits = [
+ {
+ code: 'test-div',
+ mediaTypes: {
+ banner: {
+ sizes: [[300, 250]], // a display size
+ }
+ },
+ bids: [
+ {
+ bidder: "admatic",
+ params: {
+ pid: 193937152158, // publisher id without "adm-pub-" prefix
+ wid: 104276324971, // website id
+ priceType: 'gross', // default is net
+ url: window.location.href || window.top.location.href //page url from js
+ }
+ }
+ ]
+ },{
+ code: 'test-div',
+ mediaTypes: {
+ banner: {
+ sizes: [[320, 50]], // a mobile size
+ }
+ },
+ bids: [
+ {
+ bidder: "admatic",
+ params: {
+ pid: 193937152158, // publisher id without "adm-pub-" prefix
+ wid: 104276324971, // website id
+ priceType: 'gross', // default is net
+ url: window.location.href || window.top.location.href //page url from js
+ }
+ }
+ ]
+ }
+ ];
+```
diff --git a/modules/aolBidAdapter.js b/modules/aolBidAdapter.js
index 0fb5aa1a4d34..18d30685c568 100644
--- a/modules/aolBidAdapter.js
+++ b/modules/aolBidAdapter.js
@@ -2,6 +2,7 @@ import * as utils from 'src/utils';
import { registerBidder } from 'src/adapters/bidderFactory';
import { config } from 'src/config';
import { EVENTS } from 'src/constants.json';
+import { BANNER } from 'src/mediaTypes';
const AOL_BIDDERS_CODES = {
AOL: 'aol',
@@ -30,9 +31,9 @@ const SYNC_TYPES = {
}
};
-const pubapiTemplate = template`//${'host'}/pubapi/3.0/${'network'}/${'placement'}/${'pageid'}/${'sizeid'}/ADTECH;v=2;cmd=bid;cors=yes;alias=${'alias'}${'bidfloor'}${'keyValues'};misc=${'misc'}`;
+const pubapiTemplate = template`//${'host'}/pubapi/3.0/${'network'}/${'placement'}/${'pageid'}/${'sizeid'}/ADTECH;v=2;cmd=bid;cors=yes;alias=${'alias'};misc=${'misc'}${'bidfloor'}${'keyValues'}${'consentData'}`;
const nexageBaseApiTemplate = template`//${'host'}/bidRequest?`;
-const nexageGetApiTemplate = template`dcn=${'dcn'}&pos=${'pos'}&cmd=bid${'ext'}`;
+const nexageGetApiTemplate = template`dcn=${'dcn'}&pos=${'pos'}&cmd=bid${'dynamicParams'}`;
const MP_SERVER_MAP = {
us: 'adserver-us.adtech.advertising.com',
eu: 'adserver-eu.adtech.advertising.com',
@@ -46,10 +47,10 @@ $$PREBID_GLOBAL$$.aolGlobals = {
pixelsDropped: false
};
-let showCpmAdjustmentWarning = (function () {
+let showCpmAdjustmentWarning = (function() {
let showCpmWarning = true;
- return function () {
+ return function() {
let bidderSettings = $$PREBID_GLOBAL$$.bidderSettings;
if (showCpmWarning && bidderSettings && bidderSettings.aol &&
typeof bidderSettings.aol.bidCpmAdjustment === 'function') {
@@ -62,28 +63,18 @@ let showCpmAdjustmentWarning = (function () {
};
})();
-function isInteger(value) {
- return typeof value === 'number' &&
- isFinite(value) &&
- Math.floor(value) === value;
-}
-
function template(strings, ...keys) {
return function(...values) {
let dict = values[values.length - 1] || {};
let result = [strings[0]];
keys.forEach(function(key, i) {
- let value = isInteger(key) ? values[key] : dict[key];
+ let value = utils.isInteger(key) ? values[key] : dict[key];
result.push(value, strings[i + 1]);
});
return result.join('');
};
}
-function isSecureProtocol() {
- return document.location.protocol === 'https:';
-}
-
function parsePixelItems(pixels) {
let itemsRegExp = /(img|iframe)[\s\S]*?src\s*=\s*("|')(.*?)\2/gi;
let tagNameRegExp = /\w*(?=\s)/;
@@ -110,39 +101,6 @@ function parsePixelItems(pixels) {
return pixelsItems;
}
-function _buildMarketplaceUrl(bid) {
- const params = bid.params;
- const serverParam = params.server;
- let regionParam = params.region || 'us';
- let server;
-
- if (!MP_SERVER_MAP.hasOwnProperty(regionParam)) {
- utils.logWarn(`Unknown region '${regionParam}' for AOL bidder.`);
- regionParam = 'us'; // Default region.
- }
-
- if (serverParam) {
- server = serverParam;
- } else {
- server = MP_SERVER_MAP[regionParam];
- }
-
- // Set region param, used by AOL analytics.
- params.region = regionParam;
-
- return pubapiTemplate({
- host: server,
- network: params.network,
- placement: parseInt(params.placement),
- pageid: params.pageId || 0,
- sizeid: params.sizeId || 0,
- alias: params.alias || utils.getUniqueIdentifierStr(),
- bidfloor: formatMarketplaceBidFloor(params.bidFloor),
- keyValues: formatMarketplaceKeyValues(params.keyValues),
- misc: new Date().getTime() // cache busting
- });
-}
-
function formatMarketplaceBidFloor(bidFloor) {
return (typeof bidFloor !== 'undefined') ? `;bidfloor=${bidFloor.toString()}` : '';
}
@@ -157,39 +115,16 @@ function formatMarketplaceKeyValues(keyValues) {
return formattedKeyValues;
}
-function _buildOneMobileBaseUrl(bid) {
- return nexageBaseApiTemplate({
- host: bid.params.host || NEXAGE_SERVER
- });
-}
-
-function _buildOneMobileGetUrl(bid) {
- let {dcn, pos} = bid.params;
- let nexageApi = _buildOneMobileBaseUrl(bid);
- if (dcn && pos) {
- let ext = '';
- if (isSecureProtocol()) {
- bid.params.ext = bid.params.ext || {};
- bid.params.ext.secure = 1;
- }
- utils._each(bid.params.ext, (value, key) => {
- ext += `&${key}=${encodeURIComponent(value)}`;
- });
- nexageApi += nexageGetApiTemplate({dcn, pos, ext});
- }
- return nexageApi;
-}
-
function _isMarketplaceBidder(bidder) {
return bidder === AOL_BIDDERS_CODES.AOL || bidder === AOL_BIDDERS_CODES.ONEDISPLAY;
}
-function _isNexageBidder(bidder) {
- return bidder === AOL_BIDDERS_CODES.AOL || bidder === AOL_BIDDERS_CODES.ONEMOBILE;
+function _isOneMobileBidder(bidderCode) {
+ return bidderCode === AOL_BIDDERS_CODES.AOL || bidderCode === AOL_BIDDERS_CODES.ONEMOBILE;
}
function _isNexageRequestPost(bid) {
- if (_isNexageBidder(bid.bidder) && bid.params.id && bid.params.imp && bid.params.imp[0]) {
+ if (_isOneMobileBidder(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) ||
@@ -198,7 +133,7 @@ function _isNexageRequestPost(bid) {
}
function _isNexageRequestGet(bid) {
- return _isNexageBidder(bid.bidder) && bid.params.dcn && bid.params.pos;
+ return _isOneMobileBidder(bid.bidder) && bid.params.dcn && bid.params.pos;
}
function isMarketplaceBid(bid) {
@@ -219,65 +154,25 @@ function resolveEndpointCode(bid) {
}
}
-function formatBidRequest(endpointCode, bid) {
- let bidRequest;
-
- switch (endpointCode) {
- case AOL_ENDPOINTS.DISPLAY.GET:
- bidRequest = {
- url: _buildMarketplaceUrl(bid),
- method: 'GET',
- ttl: ONE_DISPLAY_TTL
- };
- break;
-
- case AOL_ENDPOINTS.MOBILE.GET:
- bidRequest = {
- url: _buildOneMobileGetUrl(bid),
- method: 'GET',
- ttl: ONE_MOBILE_TTL
- };
- break;
-
- case AOL_ENDPOINTS.MOBILE.POST:
- bidRequest = {
- url: _buildOneMobileBaseUrl(bid),
- method: 'POST',
- ttl: ONE_MOBILE_TTL,
- data: bid.params,
- options: {
- contentType: 'application/json',
- customHeaders: {
- 'x-openrtb-version': '2.2'
- }
- }
- };
- break;
- }
-
- bidRequest.bidderCode = bid.bidder;
- bidRequest.bidId = bid.bidId;
- bidRequest.userSyncOn = bid.params.userSyncOn;
-
- return bidRequest;
-}
-
export const spec = {
code: AOL_BIDDERS_CODES.AOL,
aliases: [AOL_BIDDERS_CODES.ONEMOBILE, AOL_BIDDERS_CODES.ONEDISPLAY],
- isBidRequestValid: function(bid) {
+ supportedMediaTypes: [BANNER],
+ isBidRequestValid(bid) {
return isMarketplaceBid(bid) || isMobileBid(bid);
},
- buildRequests: function (bids) {
+ buildRequests(bids, bidderRequest) {
+ let consentData = bidderRequest ? bidderRequest.gdprConsent : null;
+
return bids.map(bid => {
const endpointCode = resolveEndpointCode(bid);
if (endpointCode) {
- return formatBidRequest(endpointCode, bid);
+ return this.formatBidRequest(endpointCode, bid, consentData);
}
});
},
- interpretResponse: function ({body}, bidRequest) {
+ interpretResponse({body}, bidRequest) {
showCpmAdjustmentWarning();
if (!body) {
@@ -290,17 +185,157 @@ export const spec = {
}
}
},
- _formatPixels: function (pixels) {
- let formattedPixels = pixels.replace(/<\/?script( type=('|")text\/javascript('|")|)?>/g, '');
+ getUserSyncs(options, bidResponses) {
+ let bidResponse = bidResponses[0];
- return '';
+ if (config.getConfig('aol.userSyncOn') === EVENTS.BID_RESPONSE) {
+ if (!$$PREBID_GLOBAL$$.aolGlobals.pixelsDropped && bidResponse && bidResponse.ext && bidResponse.ext.pixels) {
+ $$PREBID_GLOBAL$$.aolGlobals.pixelsDropped = true;
+
+ return parsePixelItems(bidResponse.ext.pixels);
+ }
+ }
+
+ return [];
+ },
+
+ formatBidRequest(endpointCode, bid, consentData) {
+ let bidRequest;
+
+ switch (endpointCode) {
+ case AOL_ENDPOINTS.DISPLAY.GET:
+ bidRequest = {
+ url: this.buildMarketplaceUrl(bid, consentData),
+ method: 'GET',
+ ttl: ONE_DISPLAY_TTL
+ };
+ break;
+
+ case AOL_ENDPOINTS.MOBILE.GET:
+ bidRequest = {
+ url: this.buildOneMobileGetUrl(bid, consentData),
+ method: 'GET',
+ ttl: ONE_MOBILE_TTL
+ };
+ break;
+
+ case AOL_ENDPOINTS.MOBILE.POST:
+ bidRequest = {
+ url: this.buildOneMobileBaseUrl(bid),
+ method: 'POST',
+ ttl: ONE_MOBILE_TTL,
+ data: this.buildOpenRtbRequestData(bid, consentData),
+ options: {
+ contentType: 'application/json',
+ customHeaders: {
+ 'x-openrtb-version': '2.2'
+ }
+ }
+ };
+ break;
+ }
+
+ bidRequest.bidderCode = bid.bidder;
+ bidRequest.bidId = bid.bidId;
+ bidRequest.userSyncOn = bid.params.userSyncOn;
+
+ return bidRequest;
},
- _parseBidResponse: function (response, bidRequest) {
+ buildMarketplaceUrl(bid, consentData) {
+ const params = bid.params;
+ const serverParam = params.server;
+ let regionParam = params.region || 'us';
+ let server;
+
+ if (!MP_SERVER_MAP.hasOwnProperty(regionParam)) {
+ utils.logWarn(`Unknown region '${regionParam}' for AOL bidder.`);
+ regionParam = 'us'; // Default region.
+ }
+
+ if (serverParam) {
+ server = serverParam;
+ } else {
+ server = MP_SERVER_MAP[regionParam];
+ }
+
+ // Set region param, used by AOL analytics.
+ params.region = regionParam;
+
+ return pubapiTemplate({
+ host: server,
+ network: params.network,
+ placement: parseInt(params.placement),
+ pageid: params.pageId || 0,
+ sizeid: params.sizeId || 0,
+ alias: params.alias || utils.getUniqueIdentifierStr(),
+ misc: new Date().getTime(), // cache busting,
+ bidfloor: formatMarketplaceBidFloor(params.bidFloor),
+ keyValues: formatMarketplaceKeyValues(params.keyValues),
+ consentData: this.formatMarketplaceConsentData(consentData)
+ });
+ },
+ buildOneMobileGetUrl(bid, consentData) {
+ let {dcn, pos, ext} = bid.params;
+ let nexageApi = this.buildOneMobileBaseUrl(bid);
+ if (dcn && pos) {
+ let dynamicParams = this.formatOneMobileDynamicParams(ext, consentData);
+ nexageApi += nexageGetApiTemplate({dcn, pos, dynamicParams});
+ }
+ return nexageApi;
+ },
+ buildOneMobileBaseUrl(bid) {
+ return nexageBaseApiTemplate({
+ host: bid.params.host || NEXAGE_SERVER
+ });
+ },
+ formatOneMobileDynamicParams(params = {}, consentData) {
+ if (this.isSecureProtocol()) {
+ params.secure = 1;
+ }
+
+ if (this.isConsentRequired(consentData)) {
+ params.euconsent = consentData.consentString;
+ params.gdpr = 1;
+ }
+
+ let paramsFormatted = '';
+ utils._each(params, (value, key) => {
+ paramsFormatted += `&${key}=${encodeURIComponent(value)}`;
+ });
+
+ return paramsFormatted;
+ },
+ buildOpenRtbRequestData(bid, consentData) {
+ let openRtbObject = {
+ id: bid.params.id,
+ imp: bid.params.imp
+ };
+
+ if (this.isConsentRequired(consentData)) {
+ openRtbObject.user = {
+ ext: {
+ consent: consentData.consentString
+ }
+ };
+ openRtbObject.regs = {
+ ext: {
+ gdpr: 1
+ }
+ };
+ }
+
+ return openRtbObject;
+ },
+ isConsentRequired(consentData) {
+ return !!(consentData && consentData.consentString && consentData.gdprApplies);
+ },
+ formatMarketplaceConsentData(consentData) {
+ let consentRequired = this.isConsentRequired(consentData);
+
+ return consentRequired ? `;euconsent=${consentData.consentString};gdpr=1` : '';
+ },
+
+ _parseBidResponse(response, bidRequest) {
let bidData;
try {
@@ -322,17 +357,10 @@ export const spec = {
}
}
- let ad = bidData.adm;
- if (response.ext && response.ext.pixels) {
- if (config.getConfig('aol.userSyncOn') !== EVENTS.BID_RESPONSE) {
- ad += this._formatPixels(response.ext.pixels);
- }
- }
-
- return {
+ let bidResponse = {
bidderCode: bidRequest.bidderCode,
requestId: bidRequest.bidId,
- ad: ad,
+ ad: bidData.adm,
cpm: cpm,
width: bidData.w,
height: bidData.h,
@@ -343,19 +371,28 @@ export const spec = {
netRevenue: true,
ttl: bidRequest.ttl
};
- },
- getUserSyncs: function(options, bidResponses) {
- let bidResponse = bidResponses[0];
- if (config.getConfig('aol.userSyncOn') === EVENTS.BID_RESPONSE) {
- if (!$$PREBID_GLOBAL$$.aolGlobals.pixelsDropped && bidResponse.ext && bidResponse.ext.pixels) {
- $$PREBID_GLOBAL$$.aolGlobals.pixelsDropped = true;
-
- return parsePixelItems(bidResponse.ext.pixels);
+ if (response.ext && response.ext.pixels) {
+ if (config.getConfig('aol.userSyncOn') !== EVENTS.BID_RESPONSE) {
+ bidResponse.ad += this.formatPixels(response.ext.pixels);
}
}
- return [];
+ return bidResponse;
+ },
+ formatPixels(pixels) {
+ let formattedPixels = pixels.replace(/<\/?script( type=('|")text\/javascript('|")|)?>/g, '');
+
+ return '';
+ },
+ isOneMobileBidder: _isOneMobileBidder,
+ isSecureProtocol() {
+ return document.location.protocol === 'https:';
}
};
diff --git a/modules/aolBidAdapter.md b/modules/aolBidAdapter.md
index a92e933bd36f..8a9d1e3291da 100644
--- a/modules/aolBidAdapter.md
+++ b/modules/aolBidAdapter.md
@@ -22,7 +22,6 @@ Module that connects to AOL's demand sources
params: {
placement: '3611253',
network: '9599.1',
- bidFloor: '0.80',
keyValues: {
test: 'key'
}
diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js
index 75e48d1ee0b7..82743974994a 100644
--- a/modules/appnexusBidAdapter.js
+++ b/modules/appnexusBidAdapter.js
@@ -73,6 +73,15 @@ export const spec = {
if (member > 0) {
payload.member_id = member;
}
+
+ if (bidderRequest && bidderRequest.gdprConsent) {
+ // note - objects for impbus use underscore instead of camelCase
+ payload.gdpr_consent = {
+ consent_string: bidderRequest.gdprConsent.consentString,
+ consent_required: bidderRequest.gdprConsent.gdprApplies
+ };
+ }
+
const payloadString = JSON.stringify(payload);
return {
method: 'POST',
diff --git a/modules/audienceNetworkBidAdapter.js b/modules/audienceNetworkBidAdapter.js
index 263edba878a2..612357e0e4ab 100644
--- a/modules/audienceNetworkBidAdapter.js
+++ b/modules/audienceNetworkBidAdapter.js
@@ -27,7 +27,7 @@ const isBidRequestValid = bid =>
typeof bid.params.placementId === 'string' &&
bid.params.placementId.length > 0 &&
Array.isArray(bid.sizes) && bid.sizes.length > 0 &&
- (isFullWidth(bid.params.format) ? bid.sizes.map(flattenSize).every(size => size === '300x250') : true) &&
+ (isFullWidth(bid.params.format) ? bid.sizes.map(flattenSize).some(size => size === '300x250') : true) &&
(isValidNonSizedFormat(bid.params.format) || bid.sizes.map(flattenSize).some(isValidSize));
/**
diff --git a/modules/beachfrontBidAdapter.js b/modules/beachfrontBidAdapter.js
index fc191e306d4f..6f92bc0d9763 100644
--- a/modules/beachfrontBidAdapter.js
+++ b/modules/beachfrontBidAdapter.js
@@ -121,7 +121,10 @@ function outstreamRender(bid) {
}
function getSizes(bid) {
- return utils.parseSizesInput(bid.sizes).map(size => {
+ let sizes = (isVideoBid(bid)
+ ? utils.deepAccess(bid, 'mediaTypes.video.playerSize')
+ : utils.deepAccess(bid, 'mediaTypes.banner.sizes')) || bid.sizes;
+ return utils.parseSizesInput(sizes).map(size => {
let [ width, height ] = size.split('x');
return {
w: parseInt(width, 10) || undefined,
diff --git a/modules/beachfrontBidAdapter.md b/modules/beachfrontBidAdapter.md
index 6e50737dd986..5804cb8dc0df 100644
--- a/modules/beachfrontBidAdapter.md
+++ b/modules/beachfrontBidAdapter.md
@@ -15,10 +15,10 @@ Module that connects to Beachfront's demand sources
var adUnits = [
{
code: 'test-video',
- sizes: [[640, 360]],
mediaTypes: {
video: {
- context: 'instream'
+ context: 'instream',
+ playerSize: [ 640, 360 ]
}
},
bids: [
@@ -28,14 +28,18 @@ Module that connects to Beachfront's demand sources
bidfloor: 0.01,
appId: '11bc5dd5-7421-4dd8-c926-40fa653bec76',
video: {
- mimes: ['video/mp4', 'application/javascript']
+ mimes: [ 'video/mp4', 'application/javascript' ]
}
}
}
]
}, {
code: 'test-banner',
- sizes: [300, 250],
+ mediaTypes: {
+ banner: {
+ sizes: [ 300, 250 ]
+ }
+ },
bids: [
{
bidder: 'beachfront',
diff --git a/modules/bridgewellBidAdapter.js b/modules/bridgewellBidAdapter.js
index 2a7dc0b35c35..712b00ec51ad 100644
--- a/modules/bridgewellBidAdapter.js
+++ b/modules/bridgewellBidAdapter.js
@@ -3,7 +3,7 @@ import {registerBidder} from 'src/adapters/bidderFactory';
import find from 'core-js/library/fn/array/find';
const BIDDER_CODE = 'bridgewell';
-const REQUEST_ENDPOINT = '//rec.scupio.com/recweb/prebid.aspx';
+const REQUEST_ENDPOINT = '//rec.scupio.com/recweb/prebid.aspx?cb=' + Math.random();
export const spec = {
code: BIDDER_CODE,
@@ -43,17 +43,23 @@ export const spec = {
* @return ServerRequest Info describing the request to the server.
*/
buildRequests: function(validBidRequests) {
- const channelIDs = [];
-
+ const adUnits = [];
utils._each(validBidRequests, function(bid) {
- channelIDs.push(bid.params.ChannelID);
+ adUnits.push({
+ ChannelID: bid.params.ChannelID,
+ mediaTypes: bid.mediaTypes || {
+ banner: {
+ sizes: bid.sizes
+ }
+ }
+ });
});
return {
- method: 'GET',
+ method: 'POST',
url: REQUEST_ENDPOINT,
data: {
- 'ChannelID': channelIDs.join(',')
+ adUnits: adUnits
},
validBidRequests: validBidRequests
};
@@ -77,15 +83,35 @@ export const spec = {
return;
}
- const anotherFormatSize = []; // for store width and height
let matchedResponse = find(serverResponse.body, function(res) {
- return !!res && !res.consumed && find(req.sizes, function(size) {
- let width = res.width;
- let height = res.height;
- if (typeof size === 'number') anotherFormatSize.push(size); // if sizes format is Array[Number], push width and height into anotherFormatSize
- return (width === size[0] && height === size[1]) || // for format Array[Array[Number]] check
- (width === anotherFormatSize[0] && height === anotherFormatSize[1]); // for foramt Array[Number] check
- });
+ let valid = false;
+
+ if (!!res && !res.consumed) { // response exists and not consumed
+ if (res.width && res.height) {
+ let mediaTypes = req.mediaTypes;
+ // for prebid 1.0 and later usage, mediaTypes.banner.sizes
+ let sizes = mediaTypes && mediaTypes.banner && mediaTypes.banner.sizes ? mediaTypes.banner.sizes : req.sizes;
+ if (sizes) {
+ let sizeValid;
+ let width = res.width;
+ let height = res.height;
+ // check response size validation
+ if (typeof sizes[0] === 'number') { // for foramt Array[Number] check
+ sizeValid = width === sizes[0] && height === sizes[1];
+ } else { // for format Array[Array[Number]] check
+ sizeValid = find(sizes, function(size) {
+ return (width === size[0] && height === size[1]);
+ });
+ }
+
+ if (sizeValid) { // dont care native sizes
+ valid = true;
+ }
+ }
+ }
+ }
+
+ return valid;
});
if (matchedResponse) {
@@ -94,11 +120,9 @@ export const spec = {
// check required parameters
if (typeof matchedResponse.cpm !== 'number') {
return;
- } else if (typeof matchedResponse.width !== 'number' || typeof matchedResponse.height !== 'number') {
- return;
} else if (typeof matchedResponse.ad !== 'string') {
return;
- } else if (typeof matchedResponse.net_revenue === 'undefined') {
+ } else if (typeof matchedResponse.netRevenue !== 'boolean') {
return;
} else if (typeof matchedResponse.currency !== 'string') {
return;
@@ -111,7 +135,7 @@ export const spec = {
bidResponse.ad = matchedResponse.ad;
bidResponse.ttl = matchedResponse.ttl;
bidResponse.creativeId = matchedResponse.id;
- bidResponse.netRevenue = matchedResponse.net_revenue === 'true';
+ bidResponse.netRevenue = matchedResponse.netRevenue;
bidResponse.currency = matchedResponse.currency;
bidResponses.push(bidResponse);
diff --git a/modules/bridgewellBidAdapter.md b/modules/bridgewellBidAdapter.md
index b9d065054fa6..6e542af18a7a 100644
--- a/modules/bridgewellBidAdapter.md
+++ b/modules/bridgewellBidAdapter.md
@@ -10,41 +10,50 @@ Module that connects to Bridgewell demand source to fetch bids.
# Test Parameters
```
- var adUnits = [
- {
- code: 'test-div',
- sizes: [[300, 250]],
- bids: [
- {
- bidder: 'bridgewell',
- params: {
- ChannelID: 'CgUxMjMzOBIBNiIFcGVubnkqCQisAhD6ARoBOQ'
- }
- }
- ]
- },{
- code: 'test-div',
- sizes: [[728, 90]],
- bids: [
- {
- bidder: 'bridgewell',
- params: {
- ChannelID: 'CgUxMjMzOBIBNiIGcGVubnkzKggI2AUQWhoBOQ',
- cpmWeight: 1.5
- }
- }
- ]
- },{
- code: 'test-div',
- sizes: [728, 90],
- bids: [
- {
- bidder: 'bridgewell',
- params: {
- ChannelID: 'CgUxMjMzOBIBNiIGcGVubnkzKggI2AUQWhoBOQ'
- }
- }
- ]
- }
- ];
+ var adUnits = [{
+ code: 'test-div',
+ sizes: [
+ [300, 250]
+ ],
+ bids: [{
+ bidder: 'bridgewell',
+ params: {
+ ChannelID: 'CgUxMjMzOBIBNiIFcGVubnkqCQisAhD6ARoBOQ'
+ }
+ }]
+ }, {
+ code: 'test-div',
+ sizes: [
+ [728, 90]
+ ],
+ bids: [{
+ bidder: 'bridgewell',
+ params: {
+ ChannelID: 'CgUxMjMzOBIBNiIGcGVubnkzKggI2AUQWhoBOQ',
+ cpmWeight: 1.5
+ }
+ }]
+ }, {
+ code: 'test-div',
+ sizes: [728, 90],
+ bids: [{
+ bidder: 'bridgewell',
+ params: {
+ ChannelID: 'CgUxMjMzOBIBNiIGcGVubnkzKggI2AUQWhoBOQ'
+ }
+ }]
+ }, {
+ code: 'test-div',
+ mediaTypes: {
+ banner: {
+ sizes: [728, 90]
+ }
+ },
+ bids: [{
+ bidder: 'bridgewell',
+ params: {
+ ChannelID: 'CgUxMjMzOBIBNiIGcGVubnkzKggI2AUQWhoBOQ'
+ }
+ }]
+ }];
```
diff --git a/modules/clickforceBidAdapter.js b/modules/clickforceBidAdapter.js
index 9267540cb618..fbcd5f2685c7 100644
--- a/modules/clickforceBidAdapter.js
+++ b/modules/clickforceBidAdapter.js
@@ -61,6 +61,19 @@ export const spec = {
});
});
return cfResponses;
+ },
+ getUserSyncs: function(syncOptions, serverResponses) {
+ if (syncOptions.iframeEnabled) {
+ return [{
+ type: 'iframe',
+ url: 'https://cdn.doublemax.net/js/capmapping.htm'
+ }]
+ } else if (syncOptions.pixelEnabled) {
+ return [{
+ type: 'image',
+ url: 'https://c.doublemax.net/cm'
+ }]
+ }
}
};
registerBidder(spec);
diff --git a/modules/clickforceBidAdapter.md b/modules/clickforceBidAdapter.md
index 912f91323310..5d8a5ac81193 100644
--- a/modules/clickforceBidAdapter.md
+++ b/modules/clickforceBidAdapter.md
@@ -29,3 +29,13 @@ joey@clickforce.com.tw (MR. Joey)
}]
}];
```
+### Configuration
+
+CLICKFORCE recommend the UserSync configuration below. It's can be optimize the CPM for the request.
+```javascript
+pbjs.setConfig({
+ userSync: {
+ iframeEnabled: true,
+ syncDelay: 1000
+}});
+```
\ No newline at end of file
diff --git a/modules/colossussspBidAdapter.js b/modules/colossussspBidAdapter.js
index df011bc102dd..22b0415936cc 100644
--- a/modules/colossussspBidAdapter.js
+++ b/modules/colossussspBidAdapter.js
@@ -1,44 +1,31 @@
import { registerBidder } from 'src/adapters/bidderFactory';
+import { BANNER, NATIVE, VIDEO } from 'src/mediaTypes';
import * as utils from 'src/utils';
const BIDDER_CODE = 'colossusssp';
const URL = '//colossusssp.com/?c=o&m=multi';
const URL_SYNC = '//colossusssp.com/?c=o&m=cookie';
-let sizeObj = {
- '468x60': 1,
- '728x90': 2,
- '300x600': 10,
- '300x250': 15,
- '300x100': 19,
- '320x50': 43,
- '300x50': 44,
- '300x300': 48,
- '300x1050': 54,
- '970x90': 55,
- '970x250': 57,
- '1000x90': 58,
- '320x80': 59,
- '640x480': 65,
- '320x480': 67,
- '320x320': 72,
- '320x160': 73,
- '480x300': 83,
- '970x310': 94,
- '970x210': 96,
- '480x320': 101,
- '768x1024': 102,
- '1000x300': 113,
- '320x100': 117,
- '800x250': 118,
- '200x600': 119
-};
+function isBidResponseValid(bid) {
+ if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) {
+ return false;
+ }
-utils._each(sizeObj, (item, key) => sizeObj[item] = key);
+ switch (bid.mediaType) {
+ case BANNER:
+ return Boolean(bid.width && bid.height && bid.ad);
+ case VIDEO:
+ return Boolean(bid.vastUrl);
+ case NATIVE:
+ return Boolean(bid.native);
+ default:
+ return false;
+ }
+}
export const spec = {
code: BIDDER_CODE,
-
+ supportedMediaTypes: [BANNER, VIDEO, NATIVE],
/**
* Determines whether or not the given bid request is valid.
*
@@ -46,9 +33,7 @@ export const spec = {
* @return boolean True if this is a valid bid, and false otherwise.
*/
isBidRequestValid: (bid) => {
- return (!isNaN(bid.params.placement_id) &&
- ((bid.params.sizes !== undefined && bid.params.sizes.length > 0 && bid.params.sizes.some((sizeIndex) => sizeObj[sizeIndex] !== undefined)) ||
- (bid.sizes !== undefined && bid.sizes.length > 0 && bid.sizes.map((size) => `${size[0]}x${size[1]}`).some((size) => sizeObj[size] !== undefined))));
+ return Boolean(bid.bidId && bid.params && !isNaN(bid.params.placement_id));
},
/**
@@ -78,10 +63,12 @@ export const spec = {
};
for (let i = 0; i < validBidRequests.length; i++) {
let bid = validBidRequests[i];
- let placement = {};
- placement['placementId'] = bid.params.placement_id;
- placement['bidId'] = bid.bidId;
- placement['sizes'] = bid.sizes;
+ let placement = {
+ placementId: bid.params.placement_id,
+ bidId: bid.bidId,
+ sizes: bid.sizes,
+ traffic: bid.params.traffic || BANNER
+ };
placements.push(placement);
}
return {
@@ -103,15 +90,7 @@ export const spec = {
serverResponse = serverResponse.body;
for (let i = 0; i < serverResponse.length; i++) {
let resItem = serverResponse[i];
- if (resItem.width && !isNaN(resItem.width) &&
- resItem.height && !isNaN(resItem.height) &&
- resItem.requestId && typeof resItem.requestId === 'string' &&
- resItem.cpm && !isNaN(resItem.cpm) &&
- resItem.ad && typeof resItem.ad === 'string' &&
- resItem.ttl && !isNaN(resItem.ttl) &&
- resItem.creativeId && typeof resItem.creativeId === 'string' &&
- resItem.netRevenue && typeof resItem.netRevenue === 'boolean' &&
- resItem.currency && typeof resItem.currency === 'string') {
+ if (isBidResponseValid(resItem)) {
response.push(resItem);
}
}
diff --git a/modules/colossussspBidAdapter.md b/modules/colossussspBidAdapter.md
index 9a5b9a0fe395..4760002f0db1 100644
--- a/modules/colossussspBidAdapter.md
+++ b/modules/colossussspBidAdapter.md
@@ -18,7 +18,8 @@ Module that connects to Colossus SSP demand sources
bids: [{
bidder: 'colossusssp',
params: {
- placement_id: 0
+ placement_id: 0,
+ traffic: 'banner'
}
}]
}
diff --git a/modules/consentManagement.js b/modules/consentManagement.js
new file mode 100644
index 000000000000..c7b6ac4df922
--- /dev/null
+++ b/modules/consentManagement.js
@@ -0,0 +1,278 @@
+/**
+ * This module adds GDPR consentManagement support to prebid.js. It interacts with
+ * supported CMPs (Consent Management Platforms) to grab the user's consent information
+ * and make it available for any GDPR supported adapters to read/pass this information to
+ * their system.
+ */
+import * as utils from 'src/utils';
+import { config } from 'src/config';
+import { gdprDataHandler } from 'src/adaptermanager';
+import includes from 'core-js/library/fn/array/includes';
+
+const DEFAULT_CMP = 'iab';
+const DEFAULT_CONSENT_TIMEOUT = 10000;
+const DEFAULT_ALLOW_AUCTION_WO_CONSENT = true;
+
+export let userCMP;
+export let consentTimeout;
+export let allowAuction;
+
+let consentData;
+
+let context;
+let args;
+let nextFn;
+
+let timer;
+let haveExited;
+
+// add new CMPs here, with their dedicated lookup function
+const cmpCallMap = {
+ 'iab': lookupIabConsent
+};
+
+/**
+ * This function handles interacting with an IAB compliant CMP to obtain the consentObject value of the user.
+ * Given the async nature of the CMP's API, we pass in acting success/error callback functions to exit this function
+ * based on the appropriate result.
+ * @param {function(string)} cmpSuccess acts as a success callback when CMP returns a value; pass along consentObject (string) from CMP
+ * @param {function(string)} cmpError acts as an error callback while interacting with CMP; pass along an error message (string)
+ */
+function lookupIabConsent(cmpSuccess, cmpError) {
+ let cmpCallbacks;
+
+ // check if the CMP is located on the same window level as the prebid code.
+ // if it's found, directly call the CMP via it's API and call the cmpSuccess callback.
+ // if it's not found, assume the prebid code may be inside an iframe and the CMP code is located in a higher parent window.
+ // in this case, use the IAB's iframe locator sample code (which is slightly cutomized) to try to find the CMP and use postMessage() to communicate with the CMP.
+ if (utils.isFn(window.__cmp)) {
+ window.__cmp('getVendorConsents', null, cmpSuccess);
+ } else {
+ callCmpWhileInIframe();
+ }
+
+ function callCmpWhileInIframe() {
+ /**
+ * START OF STOCK CODE FROM IAB 1.1 CMP SPEC
+ */
+
+ // find the CMP frame
+ let f = window;
+ let cmpFrame;
+ while (!cmpFrame) {
+ try {
+ if (f.frames['__cmpLocator']) cmpFrame = f;
+ } catch (e) {}
+ if (f === window.top) break;
+ f = f.parent;
+ }
+
+ cmpCallbacks = {};
+
+ /* Setup up a __cmp function to do the postMessage and stash the callback.
+ This function behaves (from the caller's perspective identicially to the in-frame __cmp call */
+ window.__cmp = function(cmd, arg, callback) {
+ if (!cmpFrame) {
+ removePostMessageListener();
+
+ let errmsg = 'CMP not found';
+ // small customization to properly return error
+ return cmpError(errmsg);
+ }
+ let callId = Math.random() + '';
+ let msg = {__cmpCall: {
+ command: cmd,
+ parameter: arg,
+ callId: callId
+ }};
+ cmpCallbacks[callId] = callback;
+ cmpFrame.postMessage(msg, '*');
+ }
+
+ /** when we get the return message, call the stashed callback */
+ // small customization to remove this eventListener later in module
+ window.addEventListener('message', readPostMessageResponse, false);
+
+ /**
+ * END OF STOCK CODE FROM IAB 1.1 CMP SPEC
+ */
+
+ // call CMP
+ window.__cmp('getVendorConsents', null, cmpIframeCallback);
+ }
+
+ function readPostMessageResponse(event) {
+ // small customization to prevent reading strings from other sources that aren't JSON.stringified
+ let json = (typeof event.data === 'string' && includes(event.data, 'cmpReturn')) ? JSON.parse(event.data) : event.data;
+ if (json.__cmpReturn) {
+ let i = json.__cmpReturn;
+ cmpCallbacks[i.callId](i.returnValue, i.success);
+ delete cmpCallbacks[i.callId];
+ }
+ }
+
+ function removePostMessageListener() {
+ window.removeEventListener('message', readPostMessageResponse, false);
+ }
+
+ function cmpIframeCallback(consentObject) {
+ removePostMessageListener();
+ cmpSuccess(consentObject);
+ }
+}
+
+/**
+ * If consentManagement module is enabled (ie included in setConfig), this hook function will attempt to fetch the
+ * user's encoded consent string from the supported CMP. Once obtained, the module will store this
+ * data as part of a gdprConsent object which gets transferred to adaptermanager's gdprDataHandler object.
+ * This information is later added into the bidRequest object for any supported adapters to read/pass along to their system.
+ * @param {object} config required; This is the same param that's used in pbjs.requestBids.
+ * @param {function} fn required; The next function in the chain, used by hook.js
+ */
+export function requestBidsHook(config, fn) {
+ context = this;
+ args = arguments;
+ nextFn = fn;
+ haveExited = false;
+
+ // in case we already have consent (eg during bid refresh)
+ if (consentData) {
+ return exitModule();
+ }
+
+ if (!includes(Object.keys(cmpCallMap), userCMP)) {
+ utils.logWarn(`CMP framework (${userCMP}) is not a supported framework. Aborting consentManagement module and resuming auction.`);
+ return nextFn.apply(context, args);
+ }
+
+ cmpCallMap[userCMP].call(this, processCmpData, cmpFailed);
+
+ // only let this code run if module is still active (ie if the callbacks used by CMPs haven't already finished)
+ if (!haveExited) {
+ if (consentTimeout === 0) {
+ processCmpData(undefined);
+ } else {
+ timer = setTimeout(cmpTimedOut, consentTimeout);
+ }
+ }
+}
+
+/**
+ * This function checks the consent data provided by CMP to ensure it's in an expected state.
+ * If it's bad, we exit the module depending on config settings.
+ * If it's good, then we store the value and exits the module.
+ * @param {object} consentObject required; object returned by CMP that contains user's consent choices
+ */
+function processCmpData(consentObject) {
+ if (!utils.isPlainObject(consentObject) || !utils.isStr(consentObject.metadata) || consentObject.metadata === '') {
+ cmpFailed(`CMP returned unexpected value during lookup process; returned value was (${consentObject}).`);
+ } else {
+ clearTimeout(timer);
+ storeConsentData(consentObject);
+
+ exitModule();
+ }
+}
+
+/**
+ * General timeout callback when interacting with CMP takes too long.
+ */
+function cmpTimedOut() {
+ cmpFailed('CMP workflow exceeded timeout threshold.');
+}
+
+/**
+ * This function contains the controlled steps to perform when there's a problem with CMP.
+ * @param {string} errMsg required; should be a short descriptive message for why the failure/issue happened.
+*/
+function cmpFailed(errMsg) {
+ clearTimeout(timer);
+
+ // still set the consentData to undefined when there is a problem as per config options
+ if (allowAuction) {
+ storeConsentData(undefined);
+ }
+ exitModule(errMsg);
+}
+
+/**
+ * Stores CMP data locally in module and then invokes gdprDataHandler.setConsentData() to make information available in adaptermanger.js for later in the auction
+ * @param {object} cmpConsentObject required; an object representing user's consent choices (can be undefined in certain use-cases for this function only)
+ */
+function storeConsentData(cmpConsentObject) {
+ consentData = {
+ consentString: (cmpConsentObject) ? cmpConsentObject.metadata : undefined,
+ vendorData: cmpConsentObject,
+ gdprApplies: (cmpConsentObject) ? cmpConsentObject.gdprApplies : undefined
+ };
+ gdprDataHandler.setConsentData(consentData);
+}
+
+/**
+ * This function handles the exit logic for the module.
+ * There are several paths in the module's logic to call this function and we only allow 1 of the 3 potential exits to happen before suppressing others.
+ *
+ * We prevent multiple exits to avoid conflicting messages in the console depending on certain scenarios.
+ * One scenario could be auction was canceled due to timeout with CMP being reached.
+ * While the timeout is the accepted exit and runs first, the CMP's callback still tries to process the user's data (which normally leads to a good exit).
+ * In this case, the good exit will be suppressed since we already decided to cancel the auction.
+ *
+ * Three exit paths are:
+ * 1. good exit where auction runs (CMP data is processed normally).
+ * 2. bad exit but auction still continues (warning message is logged, CMP data is undefined and still passed along).
+ * 3. bad exit with auction canceled (error message is logged).
+ * @param {string} errMsg optional; only to be used when there was a 'bad' exit. String is a descriptive message for the failure/issue encountered.
+ */
+function exitModule(errMsg) {
+ if (haveExited === false) {
+ haveExited = true;
+
+ if (errMsg) {
+ if (allowAuction) {
+ utils.logWarn(errMsg + ' Resuming auction without consent data as per consentManagement config.');
+ nextFn.apply(context, args);
+ } else {
+ utils.logError(errMsg + ' Canceling auction as per consentManagement config.');
+ }
+ } else {
+ nextFn.apply(context, args);
+ }
+ }
+}
+
+/**
+ * Simply resets the module's consentData variable back to undefined, mainly for testing purposes
+ */
+export function resetConsentData() {
+ consentData = undefined;
+}
+
+/**
+ * A configuration function that initializes some module variables, as well as add a hook into the requestBids function
+ * @param {object} config required; consentManagement module config settings; cmp (string), timeout (int), allowAuctionWithoutConsent (boolean)
+ */
+export function setConfig(config) {
+ if (utils.isStr(config.cmpApi)) {
+ userCMP = config.cmpApi;
+ } else {
+ userCMP = DEFAULT_CMP;
+ utils.logInfo(`consentManagement config did not specify cmp. Using system default setting (${DEFAULT_CMP}).`);
+ }
+
+ if (utils.isNumber(config.timeout)) {
+ consentTimeout = config.timeout;
+ } else {
+ consentTimeout = DEFAULT_CONSENT_TIMEOUT;
+ utils.logInfo(`consentManagement config did not specify timeout. Using system default setting (${DEFAULT_CONSENT_TIMEOUT}).`);
+ }
+
+ if (typeof config.allowAuctionWithoutConsent === 'boolean') {
+ allowAuction = config.allowAuctionWithoutConsent;
+ } else {
+ allowAuction = DEFAULT_ALLOW_AUCTION_WO_CONSENT;
+ utils.logInfo(`consentManagement config did not specify allowAuctionWithoutConsent. Using system default setting (${DEFAULT_ALLOW_AUCTION_WO_CONSENT}).`);
+ }
+
+ $$PREBID_GLOBAL$$.requestBids.addHook(requestBidsHook, 50);
+}
+config.getConfig('consentManagement', config => setConfig(config.consentManagement));
diff --git a/modules/consumableBidAdapter.js b/modules/consumableBidAdapter.js
new file mode 100644
index 000000000000..e6df1daedbd7
--- /dev/null
+++ b/modules/consumableBidAdapter.js
@@ -0,0 +1,177 @@
+import * as utils from 'src/utils';
+import { registerBidder } from 'src/adapters/bidderFactory';
+import { config } from 'src/config';
+import { EVENTS } from 'src/constants.json';
+
+const CONSUMABLE_BIDDER_CODE = 'consumable'
+
+const SYNC_TYPES = {
+ IFRAME: {
+ TAG: 'iframe',
+ TYPE: 'iframe'
+ },
+ IMAGE: {
+ TAG: 'img',
+ TYPE: 'image'
+ }
+};
+
+const pubapiTemplate = ({host, network, placement, alias}) => `//${host}/pubapi/3.0/${network}/${placement}/0/0/ADTECH;v=2;cmd=bid;cors=yes;alias=${alias};misc=${new Date().getTime()}`
+const CONSUMABLE_URL = 'adserver-us.adtech.advertising.com';
+const CONSUMABLE_TTL = 60;
+const CONSUMABLE_NETWORK = '10947.1';
+
+$$PREBID_GLOBAL$$.consumableGlobals = {
+ pixelsDropped: false
+};
+
+function parsePixelItems(pixels) {
+ let itemsRegExp = /<(img|iframe)[\s\S]*?src\s*=\s*("|')(.*?)\2/gi;
+ let tagNameRegExp = /\w*(?=\s)/;
+ let srcRegExp = /src=("|')(.*?)\1/;
+ let pixelsItems = [];
+
+ if (pixels) {
+ let matchedItems = pixels.match(itemsRegExp);
+ if (matchedItems) {
+ matchedItems.forEach(item => {
+ let tagName = item.match(tagNameRegExp)[0];
+ let url = item.match(srcRegExp)[2];
+
+ if (tagName && url) {
+ pixelsItems.push({
+ type: tagName === SYNC_TYPES.IMAGE.TAG ? SYNC_TYPES.IMAGE.TYPE : SYNC_TYPES.IFRAME.TYPE,
+ url: url
+ });
+ }
+ });
+ }
+ }
+
+ return pixelsItems;
+}
+
+function _buildConsumableUrl(bid) {
+ const params = bid.params;
+
+ return pubapiTemplate({
+ host: CONSUMABLE_URL,
+ network: params.network || CONSUMABLE_NETWORK,
+ placement: parseInt(params.placement, 10)
+ });
+}
+
+function formatBidRequest(bid) {
+ let bidRequest;
+
+ bidRequest = {
+ url: _buildConsumableUrl(bid),
+ method: 'GET'
+ };
+
+ bidRequest.bidderCode = bid.bidder;
+ bidRequest.bidId = bid.bidId;
+ bidRequest.userSyncOn = bid.params.userSyncOn;
+ bidRequest.unitId = bid.params.unitId;
+ bidRequest.unitName = bid.params.unitName;
+ bidRequest.zoneId = bid.params.zoneId;
+ bidRequest.network = bid.params.network || CONSUMABLE_NETWORK;
+
+ return bidRequest;
+}
+
+function _parseBidResponse (response, bidRequest) {
+ let bidData;
+ try {
+ bidData = response.seatbid[0].bid[0];
+ } catch (e) {
+ return;
+ }
+
+ let cpm;
+
+ if (bidData.ext && bidData.ext.encp) {
+ cpm = bidData.ext.encp;
+ } else {
+ cpm = bidData.price;
+
+ if (cpm === null || isNaN(cpm)) {
+ utils.logError('Invalid cpm in bid response', CONSUMABLE_BIDDER_CODE, bid);
+ return;
+ }
+ }
+ cpm = cpm * (parseFloat(bidRequest.zoneId) / parseFloat(bidRequest.network));
+
+ let oad = bidData.adm;
+ let cb = bidRequest.network === '9599.1' ? 7654321 : Math.round(new Date().getTime());
+ let ad = '' + oad;
+ ad += '';
+ ad += '';
+ ad += ''
+ if (response.ext && response.ext.pixels) {
+ if (config.getConfig('consumable.userSyncOn') !== EVENTS.BID_RESPONSE) {
+ ad += _formatPixels(response.ext.pixels);
+ }
+ }
+
+ return {
+ bidderCode: bidRequest.bidderCode,
+ requestId: bidRequest.bidId,
+ ad: ad,
+ cpm: cpm,
+ width: bidData.w,
+ height: bidData.h,
+ creativeId: bidData.crid,
+ pubapiId: response.id,
+ currency: response.cur,
+ dealId: bidData.dealid,
+ netRevenue: true,
+ ttl: CONSUMABLE_TTL
+ };
+}
+
+function _formatPixels (pixels) {
+ let formattedPixels = pixels.replace(/<\/?script( type=('|")text\/javascript('|")|)?>/g, '');
+
+ return '';
+}
+
+export const spec = {
+ code: CONSUMABLE_BIDDER_CODE,
+ isBidRequestValid: function(bid) {
+ return bid.params && bid.params.placement
+ },
+ buildRequests: function (bids) {
+ return bids.map(formatBidRequest);
+ },
+ interpretResponse: function ({body}, bidRequest) {
+ if (!body) {
+ utils.logError('Empty bid response', bidRequest.bidderCode, body);
+ } else {
+ let bid = _parseBidResponse(body, bidRequest);
+ if (bid) {
+ return bid;
+ }
+ }
+ },
+ getUserSyncs: function(options, bidResponses) {
+ let bidResponse = bidResponses[0];
+
+ if (config.getConfig('consumable.userSyncOn') === EVENTS.BID_RESPONSE) {
+ if (!$$PREBID_GLOBAL$$.consumableGlobals.pixelsDropped && bidResponse.ext && bidResponse.ext.pixels) {
+ $$PREBID_GLOBAL$$.consumableGlobals.pixelsDropped = true;
+
+ return parsePixelItems(bidResponse.ext.pixels);
+ }
+ }
+
+ return [];
+ }
+};
+
+registerBidder(spec);
diff --git a/modules/consumableBidAdapter.md b/modules/consumableBidAdapter.md
new file mode 100644
index 000000000000..f6dfe8131c69
--- /dev/null
+++ b/modules/consumableBidAdapter.md
@@ -0,0 +1,32 @@
+# Overview
+
+Module Name: Consumable Bid Adapter
+
+Module Type: Consumable Adapter
+
+Maintainer: naffis@consumable.com
+
+# Description
+
+Module that connects to Consumable's demand sources
+
+# Test Parameters
+```javascript
+ var adUnits = [
+ {
+ code: 'test-ad-div',
+ sizes: [[300, 250]],
+ bids: [
+ {
+ bidder: 'consumable',
+ params: {
+ placement: '1234567',
+ unitId: '1234',
+ unitName: 'cnsmbl-300x250',
+ zoneId: '13136.52'
+ }
+ }
+ ]
+ }
+ ];
+```
diff --git a/modules/dfpAdServerVideo.js b/modules/dfpAdServerVideo.js
index d1c668331049..1c9dbe1fac45 100644
--- a/modules/dfpAdServerVideo.js
+++ b/modules/dfpAdServerVideo.js
@@ -80,7 +80,7 @@ export default function buildDfpVideoUrl(options) {
const derivedParams = {
correlator: Date.now(),
sz: parseSizesInput(adUnit.sizes).join('|'),
- url: location.href,
+ url: encodeURIComponent(location.href),
};
const encodedCustomParams = getCustParams(bid, options);
@@ -152,7 +152,7 @@ function getCustParams(bid, options) {
adserverTargeting,
{ hb_uuid: bid && bid.videoCacheKey },
// hb_uuid will be deprecated and replaced by hb_cache_id
- {hb_cache_id: bid && bid.videoCacheKey},
+ { hb_cache_id: bid && bid.videoCacheKey },
optCustParams,
);
return encodeURIComponent(formatQS(customParams));
diff --git a/modules/dgadsBidAdapter.js b/modules/dgadsBidAdapter.js
new file mode 100644
index 000000000000..7d47cc7acf68
--- /dev/null
+++ b/modules/dgadsBidAdapter.js
@@ -0,0 +1,88 @@
+import {registerBidder} from 'src/adapters/bidderFactory';
+import * as utils from 'src/utils';
+import { BANNER, NATIVE } from 'src/mediaTypes';
+
+const BIDDER_CODE = 'dgads';
+const ENDPOINT = 'https://ads-tr.bigmining.com/ad/p/bid';
+
+export const spec = {
+ code: BIDDER_CODE,
+ supportedMediaTypes: [ BANNER, NATIVE ],
+ isBidRequestValid: function(bid) {
+ const params = bid.params;
+ if (!/^\d+$/.test(params.location_id)) {
+ return false;
+ }
+ if (!/^\d+$/.test(params.site_id)) {
+ return false;
+ }
+ return true;
+ },
+ buildRequests: function(bidRequests) {
+ if (bidRequests.length === 0) {
+ return {};
+ }
+
+ return bidRequests.map(bidRequest => {
+ const params = bidRequest.params;
+ const data = {};
+
+ data['location_id'] = params.location_id;
+ data['site_id'] = params.site_id;
+ data['transaction_id'] = bidRequest.transactionId;
+ data['bid_id'] = bidRequest.bidId;
+
+ return {
+ method: 'POST',
+ url: ENDPOINT,
+ data,
+ };
+ });
+ },
+ interpretResponse: function(serverResponse, bidRequest) {
+ const bidResponses = [];
+ const responseObj = serverResponse.body;
+ const ads = responseObj.bids;
+ let bidResponse = {};
+ if (utils.isEmpty(ads)) {
+ return [];
+ }
+ utils._each(ads, function(ad) {
+ bidResponse.requestId = ad.bidId;
+ bidResponse.bidderCode = BIDDER_CODE;
+ bidResponse.cpm = ad.cpm;
+ bidResponse.creativeId = ad.creativeId;
+ bidResponse.currency = 'JPY';
+ bidResponse.netRevenue = true;
+ bidResponse.ttl = ad.ttl;
+ bidResponse.referrer = utils.getTopWindowUrl();
+ if (ad.isNative == 1) {
+ bidResponse.mediaType = NATIVE;
+ bidResponse.native = setNativeResponse(ad);
+ } else {
+ bidResponse.width = parseInt(ad.w);
+ bidResponse.height = parseInt(ad.h);
+ bidResponse.ad = ad.ad;
+ }
+ bidResponses.push(bidResponse);
+ });
+ return bidResponses;
+ }
+};
+function setNativeResponse(ad) {
+ let nativeResponce = {};
+ nativeResponce.image = {
+ url: ad.image,
+ width: parseInt(ad.w),
+ height: parseInt(ad.h)
+ }
+ nativeResponce.title = ad.title;
+ nativeResponce.body = ad.desc;
+ nativeResponce.sponsoredBy = ad.sponsoredBy;
+ nativeResponce.clickUrl = ad.clickUrl;
+ nativeResponce.clickTrackers = ad.clickTrackers || [];
+ nativeResponce.impressionTrackers = ad.impressionTrackers || [];
+ return nativeResponce;
+}
+
+registerBidder(spec);
diff --git a/modules/dgadsBidAdapter.md b/modules/dgadsBidAdapter.md
new file mode 100644
index 000000000000..b1544007a43d
--- /dev/null
+++ b/modules/dgadsBidAdapter.md
@@ -0,0 +1,65 @@
+# Overview
+
+```
+Module Name: Digital Garage Ads Platform Bidder Adapter
+Module Type: Bidder Adapter
+Maintainer:dgads-support@garage.co.jp
+```
+
+# Description
+
+Connect to Digital Garage Ads Platform for bids.
+This adapter supports Banner and Native.
+
+# Test Parameters
+```
+ var adUnits = [
+ // Banner
+ {
+ code: 'banner-div',
+ sizes: [[300, 250]],
+ bids: [{
+ bidder: 'dgads',
+ mediaTypes: 'banner',
+ params: {
+ location_id: '1',
+ site_id: '1'
+ }
+ }]
+ },
+ // Native
+ {
+ code: 'native-div',
+ sizes: [[300, 250]],
+ mediaTypes: {
+ native: {
+ title: {
+ required: true,
+ len: 25
+ },
+ body: {
+ required: true,
+ len: 140
+ },
+ sponsoredBy: {
+ required: true,
+ len: 40
+ },
+ image: {
+ required: true
+ },
+ clickUrl: {
+ required: true
+ },
+ }
+ },
+ bids: [{
+ bidder: 'dgads',
+ params: {
+ location_id: '10',
+ site_id: '1'
+ }
+ }]
+ },
+ ];
+```
diff --git a/modules/getintentBidAdapter.js b/modules/getintentBidAdapter.js
index f677b1075292..611e8eebd6eb 100644
--- a/modules/getintentBidAdapter.js
+++ b/modules/getintentBidAdapter.js
@@ -1,4 +1,5 @@
import { registerBidder } from 'src/adapters/bidderFactory';
+import { isInteger } from 'src/utils';
const BIDDER_CODE = 'getintent';
const IS_NET_REVENUE = true;
@@ -49,7 +50,7 @@ export const spec = {
* Callback for bids, after the call to DSP completes.
* Parse the response from the server into a list of bids.
*
- * @param {object} serverResponse A response from the server.
+ * @param {object} serverResponse A response from the GetIntent's server.
* @return {Bid[]} An array of bids which were nested inside the server.
*/
interpretResponse: function(serverResponse) {
@@ -127,16 +128,31 @@ function addOptional(params, request, props) {
}
}
+/**
+ * @param {String} s The string representing a size (e.g. "300x250").
+ * @return {Number[]} An array with two elements: [width, height] (e.g.: [300, 250]).
+ * */
function parseSize(s) {
return s.split('x').map(Number);
}
-function produceSize(sizes) {
- // TODO: add support for multiple sizes
- if (Array.isArray(sizes[0])) {
- return sizes[0].join('x');
+/**
+ * @param {Array} sizes An array of sizes/numbers to be joined into single string.
+ * May be an array (e.g. [300, 250]) or array of arrays (e.g. [[300, 250], [640, 480]].
+ * @return {String} The string with sizes, e.g. array of sizes [[50, 50], [80, 80]] becomes "50x50,80x80" string.
+ * */
+function produceSize (sizes) {
+ function sizeToStr(s) {
+ if (Array.isArray(s) && s.length === 2 && isInteger(s[0]) && isInteger(s[1])) {
+ return s.join('x');
+ } else {
+ throw "Malformed parameter 'sizes'";
+ }
+ }
+ if (Array.isArray(sizes) && Array.isArray(sizes[0])) {
+ return sizes.map(sizeToStr).join(',');
} else {
- return sizes.join('x');
+ return sizeToStr(sizes);
}
}
diff --git a/modules/medianetBidAdapter.js b/modules/medianetBidAdapter.js
index e8a98721e0cc..8fe09ab74e68 100644
--- a/modules/medianetBidAdapter.js
+++ b/modules/medianetBidAdapter.js
@@ -3,18 +3,53 @@ import * as utils from 'src/utils';
import { config } from 'src/config';
const BIDDER_CODE = 'medianet';
-const BID_URL = 'https://prebid.media.net/rtb/prebid';
+const BID_URL = '//prebid.media.net/rtb/prebid';
$$PREBID_GLOBAL$$.medianetGlobals = {};
function siteDetails(site) {
site = site || {};
-
- return {
+ let siteData = {
domain: site.domain || utils.getTopWindowLocation().host,
page: site.page || utils.getTopWindowUrl(),
ref: site.ref || utils.getTopWindowReferrer()
- }
+ };
+
+ return Object.assign(siteData, getPageMeta());
+}
+
+function getPageMeta() {
+ let canonicalUrl = getUrlFromSelector('link[rel="canonical"]', 'href');
+ let ogUrl = getUrlFromSelector('meta[property="og:url"]', 'content');
+ let twitterUrl = getUrlFromSelector('meta[name="twitter:url"]', 'content');
+
+ return Object.assign({},
+ canonicalUrl && { 'canonical_url': canonicalUrl },
+ ogUrl && { 'og_url': ogUrl },
+ twitterUrl && { 'twitter_url': twitterUrl }
+ );
+}
+
+function getUrlFromSelector(selector, attribute) {
+ let attr = getAttributeFromSelector(selector, attribute);
+ return attr && getAbsoluteUrl(attr);
+}
+
+function getAttributeFromSelector(selector, attribute) {
+ try {
+ let doc = utils.getWindowTop().document;
+ let element = doc.querySelector(selector);
+ if (element !== null && element[attribute]) {
+ return element[attribute];
+ }
+ } catch (e) {}
+}
+
+function getAbsoluteUrl(url) {
+ let aTag = utils.getWindowTop().document.createElement('a');
+ aTag.href = url;
+
+ return aTag.href;
}
function filterUrlsByType(urls, type) {
@@ -65,13 +100,13 @@ function slotParams(bidRequest) {
return params;
}
-function generatePayload(bidRequests) {
+function generatePayload(bidRequests, timeout) {
return {
site: siteDetails(bidRequests[0].params.site),
ext: configuredParams(bidRequests[0].params),
id: bidRequests[0].auctionId,
imp: bidRequests.map(request => slotParams(request)),
- tmax: config.getConfig('bidderTimeout')
+ tmax: timeout
}
}
@@ -120,8 +155,9 @@ export const spec = {
* @param {BidRequest[]} bidRequests A non-empty list of bid requests which should be sent to the Server.
* @return ServerRequest Info describing the request to the server.
*/
- buildRequests: function(bidRequests) {
- let payload = generatePayload(bidRequests);
+ buildRequests: function(bidRequests, auctionData) {
+ let timeout = auctionData.timeout || config.getConfig('bidderTimeout');
+ let payload = generatePayload(bidRequests, timeout);
return {
method: 'POST',
diff --git a/modules/openxBidAdapter.js b/modules/openxBidAdapter.js
index 4a1e1f1451ba..1cc312da273e 100644
--- a/modules/openxBidAdapter.js
+++ b/modules/openxBidAdapter.js
@@ -8,7 +8,7 @@ import {parse} from 'src/url';
const SUPPORTED_AD_TYPES = [BANNER, VIDEO];
const BIDDER_CODE = 'openx';
const BIDDER_CONFIG = 'hb_pb';
-const BIDDER_VERSION = '2.0.2';
+const BIDDER_VERSION = '2.1.0';
export const spec = {
code: BIDDER_CODE,
@@ -40,10 +40,19 @@ export const spec = {
interpretResponse: function ({body: oxResponseObj}, serverRequest) {
let mediaType = getMediaTypeFromRequest(serverRequest);
- registerUserSync(mediaType, oxResponseObj);
-
return mediaType === VIDEO ? createVideoBidResponses(oxResponseObj, serverRequest.payload)
: createBannerBidResponses(oxResponseObj, serverRequest.payload);
+ },
+ getUserSyncs: function(syncOptions, responses) {
+ if (syncOptions.iframeEnabled) {
+ let url = utils.deepAccess(responses, '0.body.ads.pixels') ||
+ utils.deepAccess(responses, '0.body.pixels') ||
+ '//u.openx.net/w/1.0/pd';
+ return [{
+ type: 'iframe',
+ url: url,
+ }];
+ }
}
};
@@ -61,21 +70,10 @@ function createBannerBidResponses(oxResponseObj, {bids, startTime}) {
}
for (let i = 0; i < adUnits.length; i++) {
let adUnit = adUnits[i];
+ let adUnitIdx = parseInt(adUnit.idx, 10);
let bidResponse = {};
- if (adUnits.length === bids.length) {
- // request and response length match, directly assign the request id based on positioning
- bidResponse.requestId = bids[i].bidId;
- } else {
- for (let j = i; j < bids.length; j++) {
- let bid = bids[j];
- if (String(bid.params.unit) === String(adUnit.adunitid) && adUnitHasValidSizeFromBid(adUnit, bid) && !bid.matched) {
- // ad unit and size match, this is the correct bid response to bid
- bidResponse.requestId = bid.bidId;
- bid.matched = true;
- break;
- }
- }
- }
+
+ bidResponse.requestId = bids[adUnitIdx].bidId;
if (adUnit.pub_rev) {
bidResponse.cpm = Number(adUnit.pub_rev) / 1000;
@@ -125,27 +123,6 @@ function buildQueryStringFromParams(params) {
.join('&');
}
-function adUnitHasValidSizeFromBid(adUnit, bid) {
- let sizes = utils.parseSizesInput(bid.sizes);
- if (!sizes) {
- return false;
- }
- let found = false;
- let creative = adUnit.creative && adUnit.creative[0];
- let creative_size = String(creative.width) + 'x' + String(creative.height);
-
- if (utils.isArray(sizes)) {
- for (let i = 0; i < sizes.length; i++) {
- let size = sizes[i];
- if (String(size) === String(creative_size)) {
- found = true;
- break;
- }
- }
- }
- return found;
-}
-
function getViewportDimensions(isIfr) {
let width;
let height;
@@ -201,14 +178,6 @@ function getMediaTypeFromRequest(serverRequest) {
return /avjp$/.test(serverRequest.url) ? VIDEO : BANNER;
}
-function registerUserSync(mediaType, responseObj) {
- if (mediaType === VIDEO && responseObj.pixels) {
- userSync.registerSync('iframe', BIDDER_CODE, responseObj.pixels);
- } else if (utils.deepAccess(responseObj, 'ads.pixels')) {
- userSync.registerSync('iframe', BIDDER_CODE, responseObj.ads.pixels);
- }
-}
-
function buildCommonQueryParamsFromBids(bids) {
const isInIframe = utils.inIframe();
diff --git a/modules/pre1api.js b/modules/pre1api.js
index 707d10fbfd88..a8aa1f31e70a 100644
--- a/modules/pre1api.js
+++ b/modules/pre1api.js
@@ -124,7 +124,7 @@ pbjs.requestBids.addHook((config, next = config) => {
} else {
logWarn(`${MODULE_NAME} module: concurrency has been disabled and "$$PREBID_GLOBAL$$.requestBids" call was queued`);
}
-}, 100);
+}, 5);
Object.keys(auctionPropMap).forEach(prop => {
if (prop === 'allBidsAvailable') {
diff --git a/modules/prebidServerBidAdapter.js b/modules/prebidServerBidAdapter.js
index 7823dcfc1cf9..2dd9485759e8 100644
--- a/modules/prebidServerBidAdapter.js
+++ b/modules/prebidServerBidAdapter.js
@@ -149,6 +149,20 @@ function doBidderSync(type, url, bidder) {
}
}
+/**
+ * Do client-side syncs for bidders.
+ *
+ * @param {Array} bidders a list of bidder names
+ */
+function doClientSideSyncs(bidders) {
+ bidders.forEach(bidder => {
+ let clientAdapter = adaptermanager.getBidAdapter(bidder);
+ if (clientAdapter && clientAdapter.registerSyncs) {
+ clientAdapter.registerSyncs([]);
+ }
+ });
+}
+
/**
* Try to convert a value to a type.
* If it can't be done, the value will be returned.
@@ -205,6 +219,10 @@ const paramTypes = {
'secure': tryConvertNumber,
'mobile': tryConvertNumber
},
+ 'openx': {
+ 'unit': tryConvertString,
+ 'customFloor': tryConvertNumber
+ },
};
/*
@@ -286,7 +304,7 @@ function transformHeightWidth(adUnit) {
*/
const LEGACY_PROTOCOL = {
- buildRequest(s2sBidRequest, adUnits) {
+ buildRequest(s2sBidRequest, bidRequests, adUnits) {
// pbs expects an ad_unit.video attribute if the imp is video
adUnits.forEach(adUnit => {
adUnit.sizes = transformHeightWidth(adUnit);
@@ -325,6 +343,7 @@ const LEGACY_PROTOCOL = {
interpretResponse(result, bidRequests, requestedBidders) {
const bids = [];
+ let responseTimes = {};
if (result.status === 'OK' || result.status === 'no_cookie') {
if (result.bidder_status) {
@@ -335,17 +354,11 @@ const LEGACY_PROTOCOL = {
if (bidder.error) {
utils.logWarn(`Prebid Server returned error: '${bidder.error}' for ${bidder.bidder}`);
}
+
+ responseTimes[bidder.bidder] = bidder.response_time_ms;
});
}
- // do client-side syncs if available
- requestedBidders.forEach(bidder => {
- let clientAdapter = adaptermanager.getBidAdapter(bidder);
- if (clientAdapter && clientAdapter.registerSyncs) {
- clientAdapter.registerSyncs([]);
- }
- });
-
if (result.bids) {
result.bids.forEach(bidObj => {
const bidRequest = utils.getBidRequest(bidObj.bid_id, bidRequests);
@@ -357,6 +370,9 @@ const LEGACY_PROTOCOL = {
bidObject.creative_id = bidObj.creative_id;
bidObject.bidderCode = bidObj.bidder;
bidObject.cpm = cpm;
+ if (responseTimes[bidObj.bidder]) {
+ bidObject.serverResponseTimeMs = responseTimes[bidObj.bidder];
+ }
if (bidObj.cache_id) {
bidObject.cache_id = bidObj.cache_id;
}
@@ -421,7 +437,7 @@ const OPEN_RTB_PROTOCOL = {
bidMap: {},
- buildRequest(s2sBidRequest, adUnits) {
+ buildRequest(s2sBidRequest, bidRequests, adUnits) {
let imps = [];
let aliases = {};
@@ -512,6 +528,35 @@ const OPEN_RTB_PROTOCOL = {
request.ext = { prebid: { aliases } };
}
+ if (bidRequests && bidRequests[0].gdprConsent) {
+ // note - gdprApplies & consentString may be undefined in certain use-cases for consentManagement module
+ let gdprApplies;
+ if (typeof bidRequests[0].gdprConsent.gdprApplies === 'boolean') {
+ gdprApplies = bidRequests[0].gdprConsent.gdprApplies ? 1 : 0;
+ }
+
+ if (request.regs) {
+ if (request.regs.ext) {
+ request.regs.ext.gdpr = gdprApplies;
+ } else {
+ request.regs.ext = { gdpr: gdprApplies };
+ }
+ } else {
+ request.regs = { ext: { gdpr: gdprApplies } };
+ }
+
+ let consentString = bidRequests[0].gdprConsent.consentString;
+ if (request.user) {
+ if (request.user.ext) {
+ request.user.ext.consent = consentString;
+ } else {
+ request.user.ext = { consent: consentString };
+ }
+ } else {
+ request.user = { ext: { consent: consentString } };
+ }
+ }
+
return request;
},
@@ -535,6 +580,11 @@ const OPEN_RTB_PROTOCOL = {
bidObject.bidderCode = seatbid.seat;
bidObject.cpm = cpm;
+ let serverResponseTimeMs = utils.deepAccess(response, ['ext', 'responsetimemillis', seatbid.seat].join('.'));
+ if (serverResponseTimeMs) {
+ bidObject.serverResponseTimeMs = serverResponseTimeMs;
+ }
+
if (utils.deepAccess(bid, 'ext.prebid.type') === VIDEO) {
bidObject.mediaType = VIDEO;
if (bid.adm) { bidObject.vastXml = bid.adm; }
@@ -614,7 +664,7 @@ export function PrebidServer() {
.reduce(utils.flatten)
.filter(utils.uniques);
- const request = protocolAdapter().buildRequest(s2sBidRequest, adUnitsWithSizes);
+ const request = protocolAdapter().buildRequest(s2sBidRequest, bidRequests, adUnitsWithSizes);
const requestJson = JSON.stringify(request);
ajax(
@@ -657,6 +707,7 @@ export function PrebidServer() {
}
done();
+ doClientSideSyncs(requestedBidders);
}
return Object.assign(this, {
diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js
index dfcde0475804..1f056bf0eff9 100644
--- a/modules/pubmaticBidAdapter.js
+++ b/modules/pubmaticBidAdapter.js
@@ -19,6 +19,11 @@ const CUSTOM_PARAMS = {
'verId': '' // OpenWrap Legacy: version ID
};
const NET_REVENUE = false;
+const dealChannelValues = {
+ 1: 'PMP',
+ 5: 'PREF',
+ 6: 'PMPG'
+};
let publisherId = 0;
@@ -195,7 +200,7 @@ export const spec = {
* @param {validBidRequests[]} - an array of bids
* @return ServerRequest Info describing the request to the server.
*/
- buildRequests: validBidRequests => {
+ buildRequests: (validBidRequests, bidderRequest) => {
var conf = _initConf();
var payload = _createOrtbTemplate(conf);
validBidRequests.forEach(bid => {
@@ -217,14 +222,28 @@ export const spec = {
payload.site.publisher.id = conf.pubId.trim();
publisherId = conf.pubId.trim();
payload.ext.wrapper = {};
- payload.ext.wrapper.profile = conf.profId || UNDEFINED;
- payload.ext.wrapper.version = conf.verId || UNDEFINED;
+ payload.ext.wrapper.profile = parseInt(conf.profId) || UNDEFINED;
+ payload.ext.wrapper.version = parseInt(conf.verId) || UNDEFINED;
payload.ext.wrapper.wiid = conf.wiid || UNDEFINED;
payload.ext.wrapper.wv = constants.REPO_AND_VERSION;
payload.ext.wrapper.transactionId = conf.transactionId;
payload.ext.wrapper.wp = 'pbjs';
payload.user.gender = (conf.gender ? conf.gender.trim() : UNDEFINED);
payload.user.geo = {};
+
+ // Attaching GDPR Consent Params
+ if (bidderRequest && bidderRequest.gdprConsent) {
+ payload.user.ext = {
+ consent: bidderRequest.gdprConsent.consentString
+ };
+
+ payload.regs = {
+ ext: {
+ gdpr: (bidderRequest.gdprConsent.gdprApplies ? 1 : 0)
+ }
+ };
+ }
+
payload.user.geo.lat = _parseSlotParam('lat', conf.lat);
payload.user.geo.lon = _parseSlotParam('lon', conf.lon);
payload.user.yob = _parseSlotParam('yob', conf.yob);
@@ -264,6 +283,11 @@ export const spec = {
referrer: utils.getTopWindowUrl(),
ad: bid.adm
};
+
+ if (bid.ext && bid.ext.deal_channel) {
+ newBid['dealChannel'] = dealChannelValues[bid.ext.deal_channel] || null;
+ }
+
bidResponses.push(newBid);
});
}
@@ -276,11 +300,19 @@ export const spec = {
/**
* Register User Sync.
*/
- getUserSyncs: syncOptions => {
+ getUserSyncs: (syncOptions, responses, gdprConsent) => {
+ let syncurl = USYNCURL + publisherId;
+
+ // Attaching GDPR Consent Params in UserSync url
+ if (gdprConsent) {
+ syncurl += '&gdpr=' + (gdprConsent.gdprApplies ? 1 : 0);
+ syncurl += '&consent=' + encodeURIComponent(gdprConsent.consentString || '');
+ }
+
if (syncOptions.iframeEnabled) {
return [{
type: 'iframe',
- url: USYNCURL + publisherId
+ url: syncurl
}];
} else {
utils.logWarn('PubMatic: Please enable iframe based user sync.');
diff --git a/modules/pulsepointBidAdapter.js b/modules/pulsepointBidAdapter.js
index fc637cc9fffa..94733ad78059 100644
--- a/modules/pulsepointBidAdapter.js
+++ b/modules/pulsepointBidAdapter.js
@@ -34,7 +34,7 @@ export const spec = {
!!(bid && bid.params && bid.params.cp && bid.params.ct)
),
- buildRequests: bidRequests => {
+ buildRequests: (bidRequests, bidderRequest) => {
const request = {
id: bidRequests[0].bidderRequestId,
imp: bidRequests.map(slot => impression(slot)),
@@ -42,6 +42,7 @@ export const spec = {
app: app(bidRequests),
device: device(),
};
+ applyGdpr(bidderRequest, request);
return {
method: 'POST',
url: '//bid.contextweb.com/header/ortb',
@@ -304,6 +305,16 @@ function adSize(slot) {
return [1, 1];
}
+/**
+ * Applies GDPR parameters to request.
+ */
+function applyGdpr(bidderRequest, ortbRequest) {
+ if (bidderRequest && bidderRequest.gdprConsent) {
+ ortbRequest.regs = { ext: { gdpr: bidderRequest.gdprConsent.gdprApplies ? 1 : 0 } };
+ ortbRequest.user = { ext: { consent: bidderRequest.gdprConsent.consentString } };
+ }
+}
+
/**
* Parses the native response from the Bid given.
*/
diff --git a/modules/quantumBidAdapter.js b/modules/quantumBidAdapter.js
index 242ccc63204c..f6df8a2ff614 100644
--- a/modules/quantumBidAdapter.js
+++ b/modules/quantumBidAdapter.js
@@ -99,12 +99,15 @@ export const spec = {
if (serverBody.cobj) {
bid.cobj = serverBody.cobj;
}
+ if (bidRequest.sizes) {
+ bid.width = bidRequest.sizes[0][0];
+ bid.height = bidRequest.sizes[0][1];
+ }
bid.nurl = serverBody.nurl;
bid.sync = serverBody.sync;
if (bidRequest.renderMode && bidRequest.renderMode === 'banner') {
- bid.width = 300;
- bid.height = 225;
+ bid.mediaType = 'banner';
if (serverBody.native) {
const adAssetsUrl = '//cdn.elasticad.net/native/serve/js/quantx/quantumAd/';
let assets = serverBody.native.assets;
@@ -216,6 +219,7 @@ export const spec = {
}
} else {
// native
+ bid.mediaType = 'native';
if (bidRequest.mediaType === 'native') {
if (serverBody.native) {
let assets = serverBody.native.assets;
diff --git a/modules/readpeakBidAdapter.js b/modules/readpeakBidAdapter.js
index d19570d16cac..6c0773d1f7c7 100644
--- a/modules/readpeakBidAdapter.js
+++ b/modules/readpeakBidAdapter.js
@@ -1,5 +1,7 @@
-import {logError, getTopWindowLocation} from 'src/utils';
+import { logError, getTopWindowLocation, replaceAuctionPrice, getTopWindowReferrer } from 'src/utils';
import { registerBidder } from 'src/adapters/bidderFactory';
+import { config } from 'src/config';
+import { NATIVE } from 'src/mediaTypes';
export const ENDPOINT = '//app.readpeak.com/header/prebid';
@@ -18,7 +20,7 @@ export const spec = {
code: BIDDER_CODE,
- supportedMediaTypes: ['native'],
+ supportedMediaTypes: [NATIVE],
isBidRequestValid: bid => (
!!(bid && bid.params && bid.params.publisherId && bid.nativeParams)
@@ -31,7 +33,14 @@ export const spec = {
site: site(bidRequests),
app: app(bidRequests),
device: device(),
- isPrebid: true,
+ cur: config.getConfig('currency') || ['USD'],
+ source: {
+ fd: 1,
+ tid: bidRequests[0].transactionId,
+ ext: {
+ prebid: '$prebid.version$',
+ },
+ },
}
return {
@@ -70,7 +79,7 @@ function bidResponseAvailable(bidRequest, bidResponse) {
creativeId: idToBidMap[id].crid,
ttl: 300,
netRevenue: true,
- mediaType: 'native',
+ mediaType: NATIVE,
currency: bidResponse.cur,
native: nativeResponse(idToImpMap[id], idToBidMap[id]),
};
@@ -93,7 +102,7 @@ function nativeImpression(slot) {
if (slot.nativeParams) {
const assets = [];
addAsset(assets, titleAsset(1, slot.nativeParams.title, NATIVE_DEFAULTS.TITLE_LEN));
- addAsset(assets, imageAsset(2, slot.nativeParams.image, 3, NATIVE_DEFAULTS.IMG_MIN, NATIVE_DEFAULTS.IMG_MIN));
+ addAsset(assets, imageAsset(2, slot.nativeParams.image, 3, slot.nativeParams.wmin || NATIVE_DEFAULTS.IMG_MIN, slot.nativeParams.hmin || NATIVE_DEFAULTS.IMG_MIN));
addAsset(assets, dataAsset(3, slot.nativeParams.sponsoredBy, 1, NATIVE_DEFAULTS.SPONSORED_BY_LEN));
addAsset(assets, dataAsset(4, slot.nativeParams.body, 2, NATIVE_DEFAULTS.DESCR_LEN));
addAsset(assets, dataAsset(5, slot.nativeParams.cta, 12, NATIVE_DEFAULTS.CTA_LEN));
@@ -149,19 +158,21 @@ function dataAsset(id, params, type, defaultLen) {
function site(bidderRequest) {
const pubId = bidderRequest && bidderRequest.length > 0 ? bidderRequest[0].params.publisherId : '0';
+ const siteId = bidderRequest && bidderRequest.length > 0 ? bidderRequest[0].params.siteId : '0';
const appParams = bidderRequest[0].params.app;
if (!appParams) {
return {
publisher: {
id: pubId.toString(),
+ domain: config.getConfig('publisherDomain'),
},
- id: pubId.toString(),
- ref: referrer(),
- page: getTopWindowLocation().href,
+ id: siteId ? siteId.toString() : pubId.toString(),
+ ref: getTopWindowReferrer(),
+ page: config.getConfig('pageUrl') || getTopWindowLocation().href,
domain: getTopWindowLocation().hostname
}
}
- return null;
+ return undefined;
}
function app(bidderRequest) {
@@ -177,21 +188,14 @@ function app(bidderRequest) {
domain: appParams.domain,
}
}
- return null;
-}
-
-function referrer() {
- try {
- return window.top.document.referrer;
- } catch (e) {
- return document.referrer;
- }
+ return undefined;
}
function device() {
return {
ua: navigator.userAgent,
language: (navigator.language || navigator.browserLanguage || navigator.userLanguage || navigator.systemLanguage),
+ devicetype: 1
};
}
@@ -219,13 +223,19 @@ function nativeResponse(imp, bid) {
keys.title = asset.title ? asset.title.text : keys.title;
keys.body = asset.data && asset.id === 4 ? asset.data.value : keys.body;
keys.sponsoredBy = asset.data && asset.id === 3 ? asset.data.value : keys.sponsoredBy;
- keys.image = asset.img && asset.id === 2 ? asset.img.url : keys.image;
+ keys.image = asset.img && asset.id === 2 ? {
+ url: asset.img.url,
+ width: asset.img.w || 750,
+ height: asset.img.h || 500,
+ } : keys.image;
keys.cta = asset.data && asset.id === 5 ? asset.data.value : keys.cta;
});
if (nativeAd.link) {
keys.clickUrl = encodeURIComponent(nativeAd.link.url);
}
- keys.impressionTrackers = nativeAd.imptrackers;
+ const trackers = nativeAd.imptrackers || [];
+ trackers.unshift(replaceAuctionPrice(bid.burl, bid.price));
+ keys.impressionTrackers = trackers;
return keys;
}
}
diff --git a/modules/readpeakBidAdapter.md b/modules/readpeakBidAdapter.md
index f8e010277935..a15767f29a7a 100644
--- a/modules/readpeakBidAdapter.md
+++ b/modules/readpeakBidAdapter.md
@@ -16,13 +16,14 @@ Please reach out to your account team or hello@readpeak.com for more information
# Test Parameters
```javascript
var adUnits = [{
- code: 'test-native',
+ code: '/19968336/prebid_native_example_2',
mediaTypes: { native: { type: 'image' } },
bids: [{
bidder: 'readpeak',
params: {
bidfloor: 5.00,
- publisherId: '11bc5dd5-7421-4dd8-c926-40fa653bec76'
+ publisherId: 'test',
+ siteId: 'test'
},
}]
}];
diff --git a/modules/rubiconAnalyticsAdapter.js b/modules/rubiconAnalyticsAdapter.js
index 32d1a79af5ca..dd111efe03c4 100644
--- a/modules/rubiconAnalyticsAdapter.js
+++ b/modules/rubiconAnalyticsAdapter.js
@@ -111,6 +111,7 @@ function sendMessage(auctionId, bidWonId) {
? 'server' : 'client'
},
'clientLatencyMillis',
+ 'serverLatencyMillis',
'params',
'bidResponse', bidResponse => bidResponse ? _pick(bidResponse, [
'bidPriceUSD',
@@ -386,6 +387,9 @@ let rubiconAdapter = Object.assign({}, baseAdapter, {
};
}
bid.clientLatencyMillis = Date.now() - cache.auctions[args.auctionId].timestamp;
+ if (typeof args.serverResponseTimeMs !== 'undefined') {
+ bid.serverLatencyMillis = args.serverResponseTimeMs;
+ }
bid.bidResponse = parseBidResponse(args);
break;
case BIDDER_DONE:
diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js
index 9cb4b0ab6cc3..ea88886b753f 100644
--- a/modules/rubiconBidAdapter.js
+++ b/modules/rubiconBidAdapter.js
@@ -1,7 +1,7 @@
import * as utils from 'src/utils';
-import { registerBidder } from 'src/adapters/bidderFactory';
-import { config } from 'src/config';
-import { BANNER, VIDEO } from 'src/mediaTypes';
+import {registerBidder} from 'src/adapters/bidderFactory';
+import {config} from 'src/config';
+import {BANNER, VIDEO} from 'src/mediaTypes';
const INTEGRATION = 'pbjs_lite_v$prebid.version$';
@@ -12,7 +12,7 @@ function isSecure() {
// use protocol relative urls for http or https
const FASTLANE_ENDPOINT = '//fastlane.rubiconproject.com/a/api/fastlane.json';
const VIDEO_ENDPOINT = '//fastlane-adv.rubiconproject.com/v1/auction/video';
-const SYNC_ENDPOINT = 'https://tap-secure.rubiconproject.com/partner/scripts/rubicon/emily.html?rtb_ext=1';
+const SYNC_ENDPOINT = 'https://eus.rubiconproject.com/usync.html';
const TIMEOUT_BUFFER = 500;
@@ -79,7 +79,7 @@ export const spec = {
* @param {object} bid
* @return boolean
*/
- isBidRequestValid: function(bid) {
+ isBidRequestValid: function (bid) {
if (typeof bid.params !== 'object') {
return false;
}
@@ -121,7 +121,7 @@ export const spec = {
* @param bidderRequest
* @return ServerRequest[]
*/
- buildRequests: function(bidRequests, bidderRequest) {
+ buildRequests: function (bidRequests, bidderRequest) {
return bidRequests.map(bidRequest => {
bidRequest.startTime = new Date().getTime();
@@ -134,6 +134,9 @@ export const spec = {
page_url = bidRequest.params.secure ? page_url.replace(/^http:/i, 'https:') : page_url;
+ // GDPR reference, for use by 'banner' and 'video'
+ const gdprConsent = bidderRequest.gdprConsent;
+
if (spec.hasVideoMediaType(bidRequest)) {
let params = bidRequest.params;
let size = parseSizes(bidRequest);
@@ -178,6 +181,14 @@ export const spec = {
data.slots.push(slotData);
+ if (gdprConsent) {
+ // add 'gdpr' only if 'gdprApplies' is defined
+ if (typeof gdprConsent.gdprApplies === 'boolean') {
+ data.gdpr = Number(gdprConsent.gdprApplies);
+ }
+ data.gdpr_consent = gdprConsent.consentString;
+ }
+
return {
method: 'POST',
url: VIDEO_ENDPOINT,
@@ -223,6 +234,14 @@ export const spec = {
'tk_user_key', userId
];
+ if (gdprConsent) {
+ // add 'gdpr' only if 'gdprApplies' is defined
+ if (typeof gdprConsent.gdprApplies === 'boolean') {
+ data.push('gdpr', Number(gdprConsent.gdprApplies));
+ }
+ data.push('gdpr_consent', gdprConsent.consentString);
+ }
+
if (visitor !== null && typeof visitor === 'object') {
utils._each(visitor, (item, key) => data.push(`tg_v.${key}`, item));
}
@@ -259,7 +278,7 @@ export const spec = {
* @param {BidRequest} bidRequest
* @returns {boolean}
*/
- hasVideoMediaType: function(bidRequest) {
+ hasVideoMediaType: function (bidRequest) {
return (typeof utils.deepAccess(bidRequest, 'params.video.size_id') !== 'undefined' &&
(bidRequest.mediaType === VIDEO || utils.deepAccess(bidRequest, `mediaTypes.${VIDEO}.context`) === 'instream'));
},
@@ -268,7 +287,7 @@ export const spec = {
* @param {BidRequest} bidRequest
* @return {Bid[]} An array of bids which
*/
- interpretResponse: function(responseObj, {bidRequest}) {
+ interpretResponse: function (responseObj, {bidRequest}) {
responseObj = responseObj.body
let ads = responseObj.ads;
@@ -336,7 +355,7 @@ export const spec = {
return bids;
}, []);
},
- getUserSyncs: function(syncOptions) {
+ getUserSyncs: function (syncOptions) {
if (!hasSynced && syncOptions.iframeEnabled) {
hasSynced = true;
return {
@@ -360,6 +379,7 @@ function _getDigiTrustQueryParams() {
let digiTrustUser = window.DigiTrust && (config.getConfig('digiTrustId') || window.DigiTrust.getUser({member: 'T9QSFKPDN9'}));
return (digiTrustUser && digiTrustUser.success && digiTrustUser.identity) || null;
}
+
let digiTrustId = getDigiTrustId();
// Verify there is an ID and this user has not opted out
if (!digiTrustId || (digiTrustId.privacy && digiTrustId.privacy.optout)) {
@@ -407,7 +427,7 @@ function parseSizes(bid) {
function mapSizes(sizes) {
return utils.parseSizesInput(sizes)
- // map sizes while excluding non-matches
+ // map sizes while excluding non-matches
.reduce((result, size) => {
let mappedSize = parseInt(sizeMap[size], 10);
if (mappedSize) {
@@ -448,6 +468,7 @@ export function masSizeOrdering(sizes) {
}
var hasSynced = false;
+
export function resetUserSync() {
hasSynced = false;
}
diff --git a/modules/sekindoUMBidAdapter.js b/modules/sekindoUMBidAdapter.js
index e87f3194ff09..cf8ba9e23f00 100644
--- a/modules/sekindoUMBidAdapter.js
+++ b/modules/sekindoUMBidAdapter.js
@@ -25,11 +25,17 @@ export const spec = {
*/
buildRequests: function(validBidRequests, bidderRequest) {
var pubUrl = null;
- if (parent !== window) {
- pubUrl = document.referrer;
- } else {
- pubUrl = window.location.href;
- }
+ try {
+ if (window.top == window) {
+ pubUrl = window.location.href;
+ } else {
+ try {
+ pubUrl = window.top.location.href;
+ } catch (e2) {
+ pubUrl = document.referrer;
+ }
+ }
+ } catch (e1) {}
return validBidRequests.map(bidRequest => {
var subId = utils.getBidIdParameter('subId', bidRequest.params);
diff --git a/modules/sekindoUMBidAdapter.md b/modules/sekindoUMBidAdapter.md
index 05c0227976dc..eeffff928eb9 100755
--- a/modules/sekindoUMBidAdapter.md
+++ b/modules/sekindoUMBidAdapter.md
@@ -20,7 +20,7 @@ Banner, Outstream and Native formats are supported.
params: {
spaceId: 14071
width:300, ///optional
- weight:250, //optional
+ height:250, //optional
}
}]
},
diff --git a/modules/sonobiBidAdapter.js b/modules/sonobiBidAdapter.js
index 170228dde7a0..438ab7f3a749 100644
--- a/modules/sonobiBidAdapter.js
+++ b/modules/sonobiBidAdapter.js
@@ -1,11 +1,11 @@
import { registerBidder } from 'src/adapters/bidderFactory';
-import * as utils from 'src/utils';
+import { getTopWindowLocation, parseSizesInput, logError, generateUUID, deepAccess, isEmpty } from '../src/utils';
import { BANNER, VIDEO } from '../src/mediaTypes';
import find from 'core-js/library/fn/array/find';
const BIDDER_CODE = 'sonobi';
const STR_ENDPOINT = 'https://apex.go.sonobi.com/trinity.json';
-const PAGEVIEW_ID = utils.generateUUID();
+const PAGEVIEW_ID = generateUUID();
export const spec = {
code: BIDDER_CODE,
@@ -37,7 +37,7 @@ export const spec = {
[bid.bidId]: `${slotIdentifier}|${_validateSize(bid)}${_validateFloor(bid)}`
}
} else {
- utils.logError(`The ad unit code or Sonobi Placement id for slot ${bid.bidId} is invalid`);
+ logError(`The ad unit code or Sonobi Placement id for slot ${bid.bidId} is invalid`);
}
});
@@ -46,15 +46,23 @@ export const spec = {
const payload = {
'key_maker': JSON.stringify(data),
- 'ref': utils.getTopWindowLocation().host,
- 's': utils.generateUUID(),
+ 'ref': getTopWindowLocation().host,
+ 's': generateUUID(),
'pv': PAGEVIEW_ID,
+ 'vp': _getPlatform(),
+ 'lib_name': 'prebid',
+ 'lib_v': '$prebid.version$'
};
if (validBidRequests[0].params.hfa) {
payload.hfa = validBidRequests[0].params.hfa;
}
+ // If there is no key_maker data, then dont make the request.
+ if (isEmpty(data)) {
+ return null;
+ }
+
return {
method: 'GET',
url: STR_ENDPOINT,
@@ -81,7 +89,7 @@ export const spec = {
Object.keys(bidResponse.slots).forEach(slot => {
const bidId = _getBidIdFromTrinityKey(slot);
const bidRequest = find(bidderRequests, bidReqest => bidReqest.bidId === bidId);
- const videoMediaType = utils.deepAccess(bidRequest, 'mediaTypes.video');
+ const videoMediaType = deepAccess(bidRequest, 'mediaTypes.video');
const mediaType = bidRequest.mediaType || (videoMediaType ? 'video' : null);
const createCreative = _creative(mediaType);
const bid = bidResponse.slots[slot];
@@ -138,9 +146,9 @@ export const spec = {
function _validateSize (bid) {
if (bid.params.sizes) {
- return utils.parseSizesInput(bid.params.sizes).join(',');
+ return parseSizesInput(bid.params.sizes).join(',');
}
- return utils.parseSizesInput(bid.sizes).join(',');
+ return parseSizesInput(bid.sizes).join(',');
}
function _validateSlot (bid) {
@@ -161,16 +169,42 @@ const _creative = (mediaType) => (sbi_dc, sbi_aid) => {
if (mediaType === 'video') {
return _videoCreative(sbi_dc, sbi_aid)
}
- const src = 'https://' + sbi_dc + 'apex.go.sonobi.com/sbi.js?aid=' + sbi_aid + '&as=null' + '&ref=' + utils.getTopWindowLocation().host;
+ const src = 'https://' + sbi_dc + 'apex.go.sonobi.com/sbi.js?aid=' + sbi_aid + '&as=null' + '&ref=' + getTopWindowLocation().host;
return '';
}
function _videoCreative(sbi_dc, sbi_aid) {
- return `https://${sbi_dc}apex.go.sonobi.com/vast.xml?vid=${sbi_aid}&ref=${utils.getTopWindowLocation().host}`
+ return `https://${sbi_dc}apex.go.sonobi.com/vast.xml?vid=${sbi_aid}&ref=${getTopWindowLocation().host}`
}
function _getBidIdFromTrinityKey (key) {
return key.split('|').slice(-1)[0]
}
+/**
+ * @param context - the window to determine the innerWidth from. This is purely for test purposes as it should always be the current window
+ */
+export const _isInbounds = (context = window) => (lowerBound = 0, upperBound = Number.MAX_SAFE_INTEGER) => context.innerWidth >= lowerBound && context.innerWidth < upperBound;
+
+/**
+ * @param context - the window to determine the innerWidth from. This is purely for test purposes as it should always be the current window
+ */
+export function _getPlatform(context = window) {
+ const isInBounds = _isInbounds(context);
+ const MOBILE_VIEWPORT = {
+ lt: 768
+ };
+ const TABLET_VIEWPORT = {
+ lt: 992,
+ ge: 768
+ };
+ if (isInBounds(0, MOBILE_VIEWPORT.lt)) {
+ return 'mobile'
+ }
+ if (isInBounds(TABLET_VIEWPORT.ge, TABLET_VIEWPORT.lt)) {
+ return 'tablet'
+ }
+ return 'desktop';
+}
+
registerBidder(spec);
diff --git a/modules/sovrnBidAdapter.js b/modules/sovrnBidAdapter.js
index 564dca856904..3a70a0ed4331 100644
--- a/modules/sovrnBidAdapter.js
+++ b/modules/sovrnBidAdapter.js
@@ -70,7 +70,7 @@ export const spec = {
cpm: parseFloat(sovrnBid.price),
width: parseInt(sovrnBid.w),
height: parseInt(sovrnBid.h),
- creativeId: sovrnBid.id,
+ creativeId: sovrnBid.crid || sovrnBid.id,
dealId: sovrnBid.dealid || null,
currency: 'USD',
netRevenue: true,
diff --git a/modules/yuktamediaAnalyticsAdapter.js b/modules/yuktamediaAnalyticsAdapter.js
new file mode 100644
index 000000000000..2801ec3afb8c
--- /dev/null
+++ b/modules/yuktamediaAnalyticsAdapter.js
@@ -0,0 +1,144 @@
+import { ajax } from 'src/ajax';
+import adapter from 'src/AnalyticsAdapter';
+import adaptermanager from 'src/adaptermanager';
+import CONSTANTS from 'src/constants.json';
+import * as url from 'src/url';
+import * as utils from 'src/utils';
+
+const emptyUrl = '';
+const analyticsType = 'endpoint';
+const yuktamediaAnalyticsVersion = 'v1.0.0';
+
+let initOptions;
+let auctionTimestamp;
+let events = {
+ bids: []
+};
+
+var yuktamediaAnalyticsAdapter = Object.assign(adapter(
+ {
+ emptyUrl,
+ analyticsType
+ }), {
+ track({ eventType, args }) {
+ if (typeof args !== 'undefined') {
+ if (eventType === CONSTANTS.EVENTS.BID_TIMEOUT) {
+ args.forEach(item => { mapBidResponse(item, 'timeout'); });
+ } else if (eventType === CONSTANTS.EVENTS.AUCTION_INIT) {
+ events.auctionInit = args;
+ auctionTimestamp = args.timestamp;
+ } else if (eventType === CONSTANTS.EVENTS.BID_REQUESTED) {
+ mapBidRequests(args).forEach(item => { events.bids.push(item) });
+ } else if (eventType === CONSTANTS.EVENTS.BID_RESPONSE) {
+ mapBidResponse(args, 'response');
+ } else if (eventType === CONSTANTS.EVENTS.BID_WON) {
+ send({
+ bidWon: mapBidResponse(args, 'win')
+ }, 'won');
+ }
+ }
+
+ if (eventType === CONSTANTS.EVENTS.AUCTION_END) {
+ send(events, 'auctionEnd');
+ }
+ }
+});
+
+function mapBidRequests(params) {
+ let arr = [];
+ if (typeof params.bids !== 'undefined' && params.bids.length) {
+ params.bids.forEach(function (bid) {
+ arr.push({
+ bidderCode: bid.bidder,
+ bidId: bid.bidId,
+ adUnitCode: bid.adUnitCode,
+ requestId: bid.bidderRequestId,
+ auctionId: bid.auctionId,
+ transactionId: bid.transactionId,
+ sizes: utils.parseSizesInput(bid.sizes).toString(),
+ renderStatus: 1,
+ requestTimestamp: params.auctionStart
+ });
+ });
+ }
+ return arr;
+}
+
+function mapBidResponse(bidResponse, status) {
+ if (status !== 'win') {
+ let bid = events.bids.filter(o => o.bidId == bidResponse.bidId || o.bidId == bidResponse.requestId)[0];
+ Object.assign(bid, {
+ bidderCode: bidResponse.bidder,
+ bidId: status == 'timeout' ? bidResponse.bidId : bidResponse.requestId,
+ adUnitCode: bidResponse.adUnitCode,
+ auctionId: bidResponse.auctionId,
+ creativeId: bidResponse.creativeId,
+ transactionId: bidResponse.transactionId,
+ currency: bidResponse.currency,
+ cpm: bidResponse.cpm,
+ netRevenue: bidResponse.netRevenue,
+ mediaType: bidResponse.mediaType,
+ statusMessage: bidResponse.statusMessage,
+ status: bidResponse.status,
+ renderStatus: status == 'timeout' ? 3 : 2,
+ timeToRespond: bidResponse.timeToRespond,
+ requestTimestamp: bidResponse.requestTimestamp,
+ responseTimestamp: bidResponse.responseTimestamp
+ });
+ } else if (status == 'win') {
+ return {
+ bidderCode: bidResponse.bidder,
+ bidId: bidResponse.requestId,
+ adUnitCode: bidResponse.adUnitCode,
+ auctionId: bidResponse.auctionId,
+ creativeId: bidResponse.creativeId,
+ transactionId: bidResponse.transactionId,
+ currency: bidResponse.currency,
+ cpm: bidResponse.cpm,
+ netRevenue: bidResponse.netRevenue,
+ renderedSize: bidResponse.size,
+ mediaType: bidResponse.mediaType,
+ statusMessage: bidResponse.statusMessage,
+ status: bidResponse.status,
+ renderStatus: 4,
+ timeToRespond: bidResponse.timeToRespond,
+ requestTimestamp: bidResponse.requestTimestamp,
+ responseTimestamp: bidResponse.responseTimestamp
+ }
+ }
+}
+
+function send(data, status) {
+ let location = utils.getTopWindowLocation();
+ let secure = location.protocol == 'https:';
+ if (typeof data !== 'undefined' && typeof data.auctionInit !== 'undefined') {
+ data.auctionInit = Object.assign({ host: location.host, path: location.pathname, hash: location.hash, search: location.search }, data.auctionInit);
+ }
+ data.initOptions = initOptions;
+
+ let yuktamediaAnalyticsRequestUrl = url.format({
+ protocol: secure ? 'https' : 'http',
+ hostname: 'analytics-prebid.yuktamedia.com',
+ pathname: status == 'auctionEnd' ? '/api/bids' : '/api/bid/won',
+ search: {
+ auctionTimestamp: auctionTimestamp,
+ yuktamediaAnalyticsVersion: yuktamediaAnalyticsVersion,
+ prebidVersion: $$PREBID_GLOBAL$$.version
+ }
+ });
+
+ ajax(yuktamediaAnalyticsRequestUrl, undefined, JSON.stringify(data), { method: 'POST', contentType: 'application/json' });
+}
+
+yuktamediaAnalyticsAdapter.originEnableAnalytics = yuktamediaAnalyticsAdapter.enableAnalytics;
+yuktamediaAnalyticsAdapter.enableAnalytics = function (config) {
+ initOptions = config.options;
+ yuktamediaAnalyticsAdapter.originEnableAnalytics(config);
+};
+
+adaptermanager.registerAnalyticsAdapter({
+ adapter: yuktamediaAnalyticsAdapter,
+ code: 'yuktamedia'
+});
+
+export default yuktamediaAnalyticsAdapter;
diff --git a/modules/yuktamediaAnalyticsAdapter.md b/modules/yuktamediaAnalyticsAdapter.md
new file mode 100644
index 000000000000..a21675b6b1d4
--- /dev/null
+++ b/modules/yuktamediaAnalyticsAdapter.md
@@ -0,0 +1,22 @@
+# Overview
+Module Name: YuktaMedia Analytics Adapter
+
+Module Type: Analytics Adapter
+
+Maintainer: info@yuktamedia.com
+
+# Description
+
+Analytics adapter for prebid provided by YuktaMedia. Contact info@yuktamedia.com for information.
+
+# Test Parameters
+
+```
+{
+ provider: 'yuktamedia',
+ options : {
+ pubId : 50357 //id provided by YuktaMedia LLP
+ pubKey: 'xxx' //key provided by YuktaMedia LLP
+ }
+}
+```
diff --git a/package.json b/package.json
index e417db5e22d9..92d79cdd7ea7 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "prebid.js",
- "version": "1.8.0",
+ "version": "1.9.0",
"description": "Header Bidding Management Library",
"main": "src/prebid.js",
"scripts": {
diff --git a/src/adaptermanager.js b/src/adaptermanager.js
index cef1635f1003..98d9d5fb4260 100644
--- a/src/adaptermanager.js
+++ b/src/adaptermanager.js
@@ -133,6 +133,16 @@ function getAdUnitCopyForClientAdapters(adUnits) {
return adUnitsClientCopy;
}
+exports.gdprDataHandler = {
+ consentData: null,
+ setConsentData: function(consentInfo) {
+ this.consentData = consentInfo;
+ },
+ getConsentData: function() {
+ return this.consentData;
+ }
+};
+
exports.makeBidRequests = function(adUnits, auctionStart, auctionId, cbTimeout, labels) {
let bidRequests = [];
@@ -197,6 +207,12 @@ exports.makeBidRequests = function(adUnits, auctionStart, auctionId, cbTimeout,
bidRequests.push(bidderRequest);
}
});
+
+ if (exports.gdprDataHandler.getConsentData()) {
+ bidRequests.forEach(bidRequest => {
+ bidRequest['gdprConsent'] = exports.gdprDataHandler.getConsentData();
+ });
+ }
return bidRequests;
};
diff --git a/src/adapters/bidderFactory.js b/src/adapters/bidderFactory.js
index 7540a3a33983..958173a09655 100644
--- a/src/adapters/bidderFactory.js
+++ b/src/adapters/bidderFactory.js
@@ -191,7 +191,7 @@ export function newBidder(spec) {
// As soon as that is refactored, we can move this emit event where it should be, within the done function.
events.emit(CONSTANTS.EVENTS.BIDDER_DONE, bidderRequest);
- registerSyncs(responses);
+ registerSyncs(responses, bidderRequest.gdprConsent);
}
const validBidRequests = bidderRequest.bids.filter(filterAndWarn);
@@ -327,12 +327,12 @@ export function newBidder(spec) {
}
});
- function registerSyncs(responses) {
+ function registerSyncs(responses, gdprConsent) {
if (spec.getUserSyncs) {
let syncs = spec.getUserSyncs({
iframeEnabled: config.getConfig('userSync.iframeEnabled'),
pixelEnabled: config.getConfig('userSync.pixelEnabled'),
- }, responses);
+ }, responses, gdprConsent);
if (syncs) {
if (!Array.isArray(syncs)) {
syncs = [syncs];
diff --git a/src/adloader.js b/src/adloader.js
index 6f2bd1127123..e0f2ba46cff2 100644
--- a/src/adloader.js
+++ b/src/adloader.js
@@ -1,8 +1,14 @@
-var utils = require('./utils');
-let _requestCache = {};
+import includes from 'core-js/library/fn/array/includes';
+import * as utils from './utils';
+
+const _requestCache = {};
+const _vendorWhitelist = [
+ 'criteo',
+]
/**
* Loads external javascript. Can only be used if external JS is approved by Prebid. See https://github.com/prebid/prebid-js-external-js-template#policy
+ * Each unique URL will be loaded at most 1 time.
* @param {string} url the url to load
* @param {string} moduleCode bidderCode or module code of the module requesting this resource
*/
@@ -11,18 +17,23 @@ exports.loadExternalScript = function(url, moduleCode) {
utils.logError('cannot load external script without url and moduleCode');
return;
}
+ if (!includes(_vendorWhitelist, moduleCode)) {
+ utils.logError(`${moduleCode} not whitelisted for loading external JavaScript`);
+ return;
+ }
+ // only load each asset once
+ if (_requestCache[url]) {
+ return;
+ }
+
utils.logWarn(`module ${moduleCode} is loading external JavaScript`);
const script = document.createElement('script');
script.type = 'text/javascript';
script.async = true;
-
script.src = url;
- // add the new script tag to the page
- const target = document.head || document.body;
- if (target) {
- target.appendChild(script);
- }
+ utils.insertElement(script);
+ _requestCache[url] = true;
};
/**
diff --git a/src/targeting.js b/src/targeting.js
index 498335b598c8..0ca9f949a643 100644
--- a/src/targeting.js
+++ b/src/targeting.js
@@ -22,7 +22,7 @@ export const isBidExpired = (bid) => (bid.responseTimestamp + bid.ttl * 1000 + T
const isUnusedBid = (bid) => bid && ((bid.status && !includes([BID_TARGETING_SET, RENDERED], bid.status)) || !bid.status);
// If two bids are found for same adUnitCode, we will use the latest one to take part in auction
-// This can happen in case of concurrent autions
+// This can happen in case of concurrent auctions
export const getOldestBid = function(bid, i, arr) {
let oldestBid = true;
arr.forEach((val, j) => {
diff --git a/src/utils.js b/src/utils.js
index 5b8508e52e4b..169c578a356e 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -11,6 +11,7 @@ var t_Arr = 'Array';
var t_Str = 'String';
var t_Fn = 'Function';
var t_Numb = 'Number';
+var t_Object = 'Object';
var toString = Object.prototype.toString;
let infoLogger = null;
try {
@@ -382,6 +383,10 @@ exports.isNumber = function(object) {
return this.isA(object, t_Numb);
};
+exports.isPlainObject = function(object) {
+ return this.isA(object, t_Object);
+}
+
/**
* Return if the object is "empty";
* this includes falsey, no keys, or no items at indices
diff --git a/test/spec/adloader_spec.js b/test/spec/adloader_spec.js
index 951631d7eacf..55224cb0aab6 100644
--- a/test/spec/adloader_spec.js
+++ b/test/spec/adloader_spec.js
@@ -1,4 +1,31 @@
+import * as utils from 'src/utils';
+import * as adLoader from 'src/adloader';
+
describe('adLoader', function () {
- var assert = require('chai').assert,
- adLoader = require('../../src/adloader');
+ let utilsinsertElementStub;
+ let utilsLogErrorStub;
+
+ beforeEach(() => {
+ utilsinsertElementStub = sinon.stub(utils, 'insertElement');
+ utilsLogErrorStub = sinon.stub(utils, 'logError');
+ });
+
+ afterEach(() => {
+ utilsinsertElementStub.restore();
+ utilsLogErrorStub.restore();
+ });
+
+ describe('loadExternalScript', () => {
+ it('requires moduleCode to be included on the request', () => {
+ adLoader.loadExternalScript('someURL');
+ expect(utilsLogErrorStub.called).to.be.true;
+ expect(utilsinsertElementStub.called).to.be.false;
+ });
+
+ it('only allows whitelisted vendors to load scripts', () => {
+ adLoader.loadExternalScript('someURL', 'criteo');
+ expect(utilsLogErrorStub.called).to.be.false;
+ expect(utilsinsertElementStub.called).to.be.true;
+ });
+ });
});
diff --git a/test/spec/modules/adformBidAdapter_spec.js b/test/spec/modules/adformBidAdapter_spec.js
index f3aa735be00e..21ff84bdad56 100644
--- a/test/spec/modules/adformBidAdapter_spec.js
+++ b/test/spec/modules/adformBidAdapter_spec.js
@@ -30,7 +30,7 @@ describe('Adform adapter', () => {
it('should pass multiple bids via single request', () => {
let request = spec.buildRequests(bids);
let parsedUrl = parseUrl(request.url);
- assert.lengthOf(parsedUrl.items, 5);
+ assert.lengthOf(parsedUrl.items, 7);
});
it('should handle global request parameters', () => {
@@ -62,6 +62,7 @@ describe('Adform adapter', () => {
{
mid: '2',
someVar: 'someValue',
+ pt: 'gross',
transactionId: '5f33781f-9552-4iuy'
},
{
@@ -78,6 +79,16 @@ describe('Adform adapter', () => {
mid: '3',
pdom: 'home',
transactionId: '5f33781f-9552-7ev3'
+ },
+ {
+ mid: '5',
+ pt: 'net',
+ transactionId: '5f33781f-9552-7ev3',
+ },
+ {
+ mid: '6',
+ pt: 'gross',
+ transactionId: '5f33781f-9552-7ev3'
}
]);
});
@@ -87,6 +98,27 @@ describe('Adform adapter', () => {
let request = spec.buildRequests([bids[0]]);
assert.deepEqual(resultBids, bids[0]);
});
+
+ it('should send GDPR Consent data to adform', () => {
+ var resultBids = JSON.parse(JSON.stringify(bids[0]));
+ let request = spec.buildRequests([bids[0]], {gdprConsent: {gdprApplies: 1, consentString: 'concentDataString'}});
+ let parsedUrl = parseUrl(request.url).query;
+
+ assert.equal(parsedUrl.gdpr, 1);
+ assert.equal(parsedUrl.gdpr_consent, 'concentDataString');
+ });
+
+ it('should set gross to the request, if there is any gross priceType', () => {
+ let request = spec.buildRequests([bids[5], bids[5]]);
+ let parsedUrl = parseUrl(request.url);
+
+ assert.equal(parsedUrl.query.pt, 'net');
+
+ request = spec.buildRequests([bids[4], bids[3]]);
+ parsedUrl = parseUrl(request.url);
+
+ assert.equal(parsedUrl.query.pt, 'gross');
+ });
});
describe('interpretResponse', () => {
@@ -178,7 +210,7 @@ describe('Adform adapter', () => {
beforeEach(() => {
let sizes = [[250, 300], [300, 250], [300, 600]];
let placementCode = ['div-01', 'div-02', 'div-03', 'div-04', 'div-05'];
- let params = [{ mid: 1, url: 'some// there' }, {adxDomain: null, mid: 2, someVar: 'someValue', pt: 'gross'}, { adxDomain: null, mid: 3, pdom: 'home' }];
+ let params = [{ mid: 1, url: 'some// there' }, {adxDomain: null, mid: 2, someVar: 'someValue', pt: 'gross'}, { adxDomain: null, mid: 3, pdom: 'home' }, {mid: 5, pt: 'net'}, {mid: 6, pt: 'gross'}];
bids = [
{
adUnitCode: placementCode[0],
@@ -236,6 +268,28 @@ describe('Adform adapter', () => {
placementCode: placementCode[2],
sizes: [],
transactionId: '5f33781f-9552-7ev3'
+ },
+ {
+ adUnitCode: placementCode[4],
+ auctionId: '7aefb970-2045',
+ bidId: '2a0cf6n',
+ bidder: 'adform',
+ bidderRequestId: '1ab8d9',
+ params: params[3],
+ placementCode: placementCode[2],
+ sizes: [],
+ transactionId: '5f33781f-9552-7ev3'
+ },
+ {
+ adUnitCode: placementCode[4],
+ auctionId: '7aefb970-2045',
+ bidId: '2a0cf6n',
+ bidder: 'adform',
+ bidderRequestId: '1ab8d9',
+ params: params[4],
+ placementCode: placementCode[2],
+ sizes: [],
+ transactionId: '5f33781f-9552-7ev3'
}
];
serverResponse = {
diff --git a/test/spec/modules/aolBidAdapter_spec.js b/test/spec/modules/aolBidAdapter_spec.js
index 38b36bbaf3d5..d69b9e6e3d80 100644
--- a/test/spec/modules/aolBidAdapter_spec.js
+++ b/test/spec/modules/aolBidAdapter_spec.js
@@ -98,6 +98,7 @@ describe('AolAdapter', () => {
let bidRequest;
let logWarnSpy;
let formatPixelsStub;
+ let isOneMobileBidderStub;
beforeEach(() => {
bidderSettingsBackup = $$PREBID_GLOBAL$$.bidderSettings;
@@ -110,13 +111,15 @@ describe('AolAdapter', () => {
body: getDefaultBidResponse()
};
logWarnSpy = sinon.spy(utils, 'logWarn');
- formatPixelsStub = sinon.stub(spec, '_formatPixels');
+ formatPixelsStub = sinon.stub(spec, 'formatPixels');
+ isOneMobileBidderStub = sinon.stub(spec, 'isOneMobileBidder');
});
afterEach(() => {
$$PREBID_GLOBAL$$.bidderSettings = bidderSettingsBackup;
logWarnSpy.restore();
formatPixelsStub.restore();
+ isOneMobileBidderStub.restore();
});
it('should return formatted bid response with required properties', () => {
@@ -534,10 +537,10 @@ describe('AolAdapter', () => {
});
});
- describe('_formatPixels()', () => {
+ describe('formatPixels()', () => {
it('should return pixels wrapped for dropping them once and within nested frames ', () => {
let pixels = '';
- let formattedPixels = spec._formatPixels(pixels);
+ let formattedPixels = spec.formatPixels(pixels);
expect(formattedPixels).to.equal(
'');
});
- })
+ });
+
+ describe('isOneMobileBidder()', () => {
+ it('should return false when when bidderCode is not present', () => {
+ expect(spec.isOneMobileBidder(null)).to.be.false;
+ });
+
+ it('should return false for unknown bidder code', () => {
+ expect(spec.isOneMobileBidder('unknownBidder')).to.be.false;
+ });
+
+ it('should return true for aol bidder code', () => {
+ expect(spec.isOneMobileBidder('aol')).to.be.true;
+ });
+
+ it('should return true for one mobile bidder code', () => {
+ expect(spec.isOneMobileBidder('onemobile')).to.be.true;
+ });
+ });
+
+ describe('isConsentRequired()', () => {
+ it('should return false when consentData object is not present', () => {
+ expect(spec.isConsentRequired(null)).to.be.false;
+ });
+
+ it('should return false when gdprApplies equals true and consentString is not present', () => {
+ let consentData = {
+ consentString: null,
+ gdprApplies: true
+ };
+
+ expect(spec.isConsentRequired(consentData)).to.be.false;
+ });
+
+ it('should return false when consentString is present and gdprApplies equals false', () => {
+ let consentData = {
+ consentString: 'consent-string',
+ gdprApplies: false
+ };
+
+ expect(spec.isConsentRequired(consentData)).to.be.false;
+ });
+
+ it('should return true when consentString is present and gdprApplies equals true', () => {
+ let consentData = {
+ consentString: 'consent-string',
+ gdprApplies: true
+ };
+
+ expect(spec.isConsentRequired(consentData)).to.be.true;
+ });
+ });
+
+ describe('formatMarketplaceConsentData()', () => {
+ let consentRequiredStub;
+
+ beforeEach(() => {
+ consentRequiredStub = sinon.stub(spec, 'isConsentRequired');
+ });
+
+ afterEach(() => {
+ consentRequiredStub.restore();
+ });
+
+ it('should return empty string when consent is not required', () => {
+ consentRequiredStub.returns(false);
+ expect(spec.formatMarketplaceConsentData()).to.be.equal('');
+ });
+
+ it('should return formatted consent data when consent is required', () => {
+ consentRequiredStub.returns(true);
+ let formattedConsentData = spec.formatMarketplaceConsentData({
+ consentString: 'test-consent'
+ });
+ expect(formattedConsentData).to.be.equal(';euconsent=test-consent;gdpr=1');
+ });
+ });
+
+ describe('formatOneMobileDynamicParams()', () => {
+ let consentRequiredStub;
+ let secureProtocolStub;
+
+ beforeEach(() => {
+ consentRequiredStub = sinon.stub(spec, 'isConsentRequired');
+ secureProtocolStub = sinon.stub(spec, 'isSecureProtocol');
+ });
+
+ afterEach(() => {
+ consentRequiredStub.restore();
+ secureProtocolStub.restore();
+ });
+
+ it('should return empty string when params are not present', () => {
+ expect(spec.formatOneMobileDynamicParams()).to.be.equal('');
+ });
+
+ it('should return formatted params when params are present', () => {
+ let params = {
+ param1: 'val1',
+ param2: 'val2',
+ param3: 'val3'
+ };
+ expect(spec.formatOneMobileDynamicParams(params)).to.contain('¶m1=val1¶m2=val2¶m3=val3');
+ });
+
+ it('should return formatted gdpr params when isConsentRequired returns true', () => {
+ let consentData = {
+ consentString: 'test-consent'
+ };
+ consentRequiredStub.returns(true);
+ expect(spec.formatOneMobileDynamicParams({}, consentData)).to.be.equal('&euconsent=test-consent&gdpr=1');
+ });
+
+ it('should return formatted secure param when isSecureProtocol returns true', () => {
+ secureProtocolStub.returns(true);
+ expect(spec.formatOneMobileDynamicParams()).to.be.equal('&secure=1');
+ });
+ });
});
diff --git a/test/spec/modules/appnexusBidAdapter_spec.js b/test/spec/modules/appnexusBidAdapter_spec.js
index 1ba4edfa4eab..abfd50d17467 100644
--- a/test/spec/modules/appnexusBidAdapter_spec.js
+++ b/test/spec/modules/appnexusBidAdapter_spec.js
@@ -3,8 +3,6 @@ import { spec } from 'modules/appnexusBidAdapter';
import { newBidder } from 'src/adapters/bidderFactory';
import { deepClone } from 'src/utils';
-const adloader = require('../../../src/adloader');
-
const ENDPOINT = '//ib.adnxs.com/ut/v3/prebid';
describe('AppNexusAdapter', () => {
@@ -173,7 +171,7 @@ describe('AppNexusAdapter', () => {
});
});
- it('should attache native params to the request', () => {
+ it('should attach native params to the request', () => {
let bidRequest = Object.assign({},
bidRequests[0],
{
@@ -290,7 +288,7 @@ describe('AppNexusAdapter', () => {
}]);
});
- it('should should add payment rules to the request', () => {
+ it('should add payment rules to the request', () => {
let bidRequest = Object.assign({},
bidRequests[0],
{
@@ -306,21 +304,31 @@ describe('AppNexusAdapter', () => {
expect(payload.tags[0].use_pmt_rule).to.equal(true);
});
- })
- describe('interpretResponse', () => {
- let loadScriptStub;
+ it('should add gdpr consent information to the request', () => {
+ let consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A==';
+ let bidderRequest = {
+ 'bidderCode': 'appnexus',
+ 'auctionId': '1d1a030790a475',
+ 'bidderRequestId': '22edbae2733bf6',
+ 'timeout': 3000,
+ 'gdprConsent': {
+ consentString: consentString,
+ gdprApplies: true
+ }
+ };
+ bidderRequest.bids = bidRequests;
- beforeEach(() => {
- loadScriptStub = sinon.stub(adloader, 'loadScript').callsFake((...args) => {
- args[1]();
- });
- });
+ const request = spec.buildRequests(bidRequests, bidderRequest);
+ const payload = JSON.parse(request.data);
- afterEach(() => {
- loadScriptStub.restore();
+ expect(payload.gdpr_consent).to.exist;
+ expect(payload.gdpr_consent.consent_string).to.exist.and.to.equal(consentString);
+ expect(payload.gdpr_consent.consent_required).to.exist.and.to.be.true;
});
+ })
+ describe('interpretResponse', () => {
let response = {
'version': '3.0.0',
'tags': [
diff --git a/test/spec/modules/audienceNetworkBidAdapter_spec.js b/test/spec/modules/audienceNetworkBidAdapter_spec.js
index 41a6d955c6a9..f9d46e100b1f 100644
--- a/test/spec/modules/audienceNetworkBidAdapter_spec.js
+++ b/test/spec/modules/audienceNetworkBidAdapter_spec.js
@@ -75,7 +75,7 @@ describe('AudienceNetwork adapter', () => {
it('fullwidth', () => {
expect(isBidRequestValid({
bidder,
- sizes: [[300, 250]],
+ sizes: [[300, 250], [336, 280]],
params: {
placementId,
format: 'fullwidth'
diff --git a/test/spec/modules/beachfrontBidAdapter_spec.js b/test/spec/modules/beachfrontBidAdapter_spec.js
index ddd93f8406d9..5a0345ee6e21 100644
--- a/test/spec/modules/beachfrontBidAdapter_spec.js
+++ b/test/spec/modules/beachfrontBidAdapter_spec.js
@@ -14,7 +14,6 @@ describe('BeachfrontAdapter', () => {
appId: '3b16770b-17af-4d22-daff-9606bdf2c9c3'
},
adUnitCode: 'div-gpt-ad-1460505748561-0',
- sizes: [ 300, 250 ],
bidId: '25186806a41eab',
bidderRequestId: '15bdd8d4a0ebaf',
auctionId: 'f17d62d0-e3e3-48d0-9f73-cb4ea358a309'
@@ -25,7 +24,6 @@ describe('BeachfrontAdapter', () => {
appId: '11bc5dd5-7421-4dd8-c926-40fa653bec76'
},
adUnitCode: 'div-gpt-ad-1460505748561-1',
- sizes: [ 300, 600 ],
bidId: '365088ee6d649d',
bidderRequestId: '15bdd8d4a0ebaf',
auctionId: 'f17d62d0-e3e3-48d0-9f73-cb4ea358a309'
@@ -86,11 +84,16 @@ describe('BeachfrontAdapter', () => {
});
it('should attach request data', () => {
+ const width = 640;
+ const height = 480;
const bidRequest = bidRequests[0];
- bidRequest.mediaTypes = { video: {} };
+ bidRequest.mediaTypes = {
+ video: {
+ playerSize: [ width, height ]
+ }
+ };
const requests = spec.buildRequests([ bidRequest ]);
const data = requests[0].data;
- const [ width, height ] = bidRequest.sizes;
const topLocation = utils.getTopWindowLocation();
expect(data.isPrebid).to.equal(true);
expect(data.appId).to.equal(bidRequest.params.appId);
@@ -107,8 +110,11 @@ describe('BeachfrontAdapter', () => {
const width = 640;
const height = 480;
const bidRequest = bidRequests[0];
- bidRequest.sizes = [[ width, height ]];
- bidRequest.mediaTypes = { video: {} };
+ bidRequest.mediaTypes = {
+ video: {
+ playerSize: [[ width, height ]]
+ }
+ };
const requests = spec.buildRequests([ bidRequest ]);
const data = requests[0].data;
expect(data.imp[0].video).to.deep.contain({ w: width, h: height });
@@ -118,8 +124,11 @@ describe('BeachfrontAdapter', () => {
const width = 640;
const height = 480;
const bidRequest = bidRequests[0];
- bidRequest.sizes = `${width}x${height}`;
- bidRequest.mediaTypes = { video: {} };
+ bidRequest.mediaTypes = {
+ video: {
+ playerSize: `${width}x${height}`
+ }
+ };
const requests = spec.buildRequests([ bidRequest ]);
const data = requests[0].data;
expect(data.imp[0].video).to.deep.contain({ w: width, h: height });
@@ -127,13 +136,27 @@ describe('BeachfrontAdapter', () => {
it('must handle an empty bid size', () => {
const bidRequest = bidRequests[0];
- bidRequest.sizes = [];
- bidRequest.mediaTypes = { video: {} };
+ bidRequest.mediaTypes = {
+ video: {
+ playerSize: []
+ }
+ };
const requests = spec.buildRequests([ bidRequest ]);
const data = requests[0].data;
expect(data.imp[0].video).to.deep.contain({ w: undefined, h: undefined });
});
+ it('must fall back to the size on the bid object', () => {
+ const width = 640;
+ const height = 480;
+ const bidRequest = bidRequests[0];
+ bidRequest.sizes = [ width, height ];
+ bidRequest.mediaTypes = { video: {} };
+ const requests = spec.buildRequests([ bidRequest ]);
+ const data = requests[0].data;
+ expect(data.imp[0].video).to.deep.contain({ w: width, h: height });
+ });
+
it('must override video targeting params', () => {
const bidRequest = bidRequests[0];
const mimes = ['video/webm'];
@@ -163,11 +186,16 @@ describe('BeachfrontAdapter', () => {
});
it('should attach request data', () => {
+ const width = 300;
+ const height = 250;
const bidRequest = bidRequests[0];
- bidRequest.mediaTypes = { banner: {} };
+ bidRequest.mediaTypes = {
+ banner: {
+ sizes: [ width, height ]
+ }
+ };
const requests = spec.buildRequests([ bidRequest ]);
const data = requests[0].data;
- const [ width, height ] = bidRequest.sizes;
const topLocation = utils.getTopWindowLocation();
expect(data.slots).to.deep.equal([
{
@@ -184,11 +212,14 @@ describe('BeachfrontAdapter', () => {
});
it('must parse bid size from a nested array', () => {
- const width = 640;
- const height = 480;
+ const width = 300;
+ const height = 250;
const bidRequest = bidRequests[0];
- bidRequest.sizes = [[ width, height ]];
- bidRequest.mediaTypes = { banner: {} };
+ bidRequest.mediaTypes = {
+ banner: {
+ sizes: [[ width, height ]]
+ }
+ };
const requests = spec.buildRequests([ bidRequest ]);
const data = requests[0].data;
expect(data.slots[0].sizes).to.deep.equal([
@@ -197,11 +228,14 @@ describe('BeachfrontAdapter', () => {
});
it('must parse bid size from a string', () => {
- const width = 640;
- const height = 480;
+ const width = 300;
+ const height = 250;
const bidRequest = bidRequests[0];
- bidRequest.sizes = `${width}x${height}`;
- bidRequest.mediaTypes = { banner: {} };
+ bidRequest.mediaTypes = {
+ banner: {
+ sizes: `${width}x${height}`
+ }
+ };
const requests = spec.buildRequests([ bidRequest ]);
const data = requests[0].data;
expect(data.slots[0].sizes).to.deep.equal([
@@ -211,12 +245,26 @@ describe('BeachfrontAdapter', () => {
it('must handle an empty bid size', () => {
const bidRequest = bidRequests[0];
- bidRequest.sizes = [];
- bidRequest.mediaTypes = { banner: {} };
+ bidRequest.mediaTypes = {
+ banner: {
+ sizes: []
+ }
+ };
const requests = spec.buildRequests([ bidRequest ]);
const data = requests[0].data;
expect(data.slots[0].sizes).to.deep.equal([]);
});
+
+ it('must fall back to the size on the bid object', () => {
+ const width = 300;
+ const height = 250;
+ const bidRequest = bidRequests[0];
+ bidRequest.sizes = [ width, height ];
+ bidRequest.mediaTypes = { banner: {} };
+ const requests = spec.buildRequests([ bidRequest ]);
+ const data = requests[0].data;
+ expect(data.slots[0].sizes).to.deep.contain({ w: width, h: height });
+ });
});
});
@@ -250,15 +298,20 @@ describe('BeachfrontAdapter', () => {
});
it('should return a valid video bid response', () => {
+ const width = 640;
+ const height = 480;
const bidRequest = bidRequests[0];
- bidRequest.mediaTypes = { video: {} };
+ bidRequest.mediaTypes = {
+ video: {
+ playerSize: [ width, height ]
+ }
+ };
const serverResponse = {
bidPrice: 5.00,
url: 'http://reachms.bfmio.com/getmu?aid=bid:19c4a196-fb21-4c81-9a1a-ecc5437a39da',
cmpId: '123abc'
};
const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest });
- const [ width, height ] = bidRequest.sizes;
expect(bidResponse).to.deep.equal({
requestId: bidRequest.bidId,
bidderCode: spec.code,
@@ -277,7 +330,11 @@ describe('BeachfrontAdapter', () => {
it('should return a renderer for outstream video bids', () => {
const bidRequest = bidRequests[0];
- bidRequest.mediaTypes = { video: { context: 'outstream' } };
+ bidRequest.mediaTypes = {
+ video: {
+ context: 'outstream'
+ }
+ };
const serverResponse = {
bidPrice: 5.00,
url: 'http://reachms.bfmio.com/getmu?aid=bid:19c4a196-fb21-4c81-9a1a-ecc5437a39da',
@@ -307,10 +364,16 @@ describe('BeachfrontAdapter', () => {
});
it('should return valid banner bid responses', () => {
- bidRequests[0].mediaTypes = { banner: {} };
- bidRequests[0].sizes = [[ 300, 250 ], [ 728, 90 ]];
- bidRequests[1].mediaTypes = { banner: {} };
- bidRequests[1].sizes = [[ 300, 600 ], [ 200, 200 ]];
+ bidRequests[0].mediaTypes = {
+ banner: {
+ sizes: [[ 300, 250 ], [ 728, 90 ]]
+ }
+ };
+ bidRequests[1].mediaTypes = {
+ banner: {
+ sizes: [[ 300, 600 ], [ 200, 200 ]]
+ }
+ };
const serverResponse = [{
slot: bidRequests[0].adUnitCode,
adm: '',
diff --git a/test/spec/modules/bridgewellBidAdapter_spec.js b/test/spec/modules/bridgewellBidAdapter_spec.js
index 6b95b44dfe58..8615531f88f7 100644
--- a/test/spec/modules/bridgewellBidAdapter_spec.js
+++ b/test/spec/modules/bridgewellBidAdapter_spec.js
@@ -72,6 +72,21 @@ describe('bridgewellBidAdapter', function () {
'bidId': '3150ccb55da321',
'bidderRequestId': '22edbae2733bf6',
'auctionId': '1d1a030790a475',
+ },
+ {
+ 'bidder': 'bridgewell',
+ 'params': {
+ 'ChannelID': 'CgUxMjMzOBIBNiIGcGVubnkzKggI2AUQWhoBOQ',
+ },
+ 'adUnitCode': 'adunit-code-2',
+ 'mediaTypes': {
+ 'banner': {
+ 'sizes': [728, 90]
+ }
+ },
+ 'bidId': '3150ccb55da321',
+ 'bidderRequestId': '22edbae2733bf6',
+ 'auctionId': '1d1a030790a475',
}
];
const adapter = newBidder(spec);
@@ -141,6 +156,10 @@ describe('bridgewellBidAdapter', function () {
expect(spec.isBidRequestValid(bidWithZeroCpmWeight)).to.equal(false);
});
+ it('should return false when required params not found', () => {
+ expect(spec.isBidRequestValid({})).to.equal(false);
+ });
+
it('should return false when required params are not passed', () => {
let bidWithoutCpmWeight = Object.assign({}, bidWithoutCpmWeight);
let bidWithCorrectCpmWeight = Object.assign({}, bidWithCorrectCpmWeight);
@@ -177,10 +196,16 @@ describe('bridgewellBidAdapter', function () {
describe('buildRequests', () => {
it('should attach valid params to the tag', () => {
- const request = spec.buildRequests([bidRequests[0]]);
+ const request = spec.buildRequests(bidRequests);
const payload = request.data;
+ const adUnits = payload.adUnits;
+
expect(payload).to.be.an('object');
- expect(payload).to.have.property('ChannelID').that.is.a('string');
+ expect(adUnits).to.be.an('array');
+ for (let i = 0, max_i = adUnits.length; i < max_i; i++) {
+ let adUnit = adUnits[i];
+ expect(adUnit).to.have.property('ChannelID').that.is.a('string');
+ }
});
it('should attach validBidRequests to the tag', () => {
@@ -188,79 +213,89 @@ describe('bridgewellBidAdapter', function () {
const validBidRequests = request.validBidRequests;
expect(validBidRequests).to.deep.equal(bidRequests);
});
-
- it('should attach valid params to the tag if multiple ChannelIDs are presented', () => {
- const request = spec.buildRequests(bidRequests);
- const payload = request.data;
- expect(payload).to.be.an('object');
- expect(payload).to.have.property('ChannelID').that.is.a('string');
- expect(payload.ChannelID.split(',')).to.have.lengthOf(bidRequests.length);
- });
});
describe('interpretResponse', () => {
const request = spec.buildRequests(bidRequests);
- const serverResponses = [{
- 'id': 'e5b10774-32bf-4931-85ee-05095e8cff21',
- 'bidder_code': 'bridgewell',
- 'cpm': 5.0,
- 'width': 300,
- 'height': 250,
- 'ad': 'test 300x250
',
- 'ttl': 360,
- 'net_revenue': 'true',
- 'currency': 'NTD'
- }, {
- 'id': '0e4048d3-5c74-4380-a21a-00ba35629f7d',
- 'bidder_code': 'bridgewell',
- 'cpm': 5.0,
- 'width': 728,
- 'height': 90,
- 'ad': 'test 728x90
',
- 'ttl': 360,
- 'net_revenue': 'true',
- 'currency': 'NTD'
- }, {
- 'id': '8f12c646-3b87-4326-a837-c2a76999f168',
- 'bidder_code': 'bridgewell',
- 'cpm': 5.0,
- 'width': 300,
- 'height': 250,
- 'ad': 'test 300x250
',
- 'ttl': 360,
- 'net_revenue': 'true',
- 'currency': 'NTD'
- }, {
- 'id': '8f12c646-3b87-4326-a837-c2a76999f168',
- 'bidder_code': 'bridgewell',
- 'cpm': 5.0,
- 'width': 300,
- 'height': 250,
- 'ad': 'test 300x250
',
- 'ttl': 360,
- 'net_revenue': 'true',
- 'currency': 'NTD'
- }, {
- 'id': '0e4048d3-5c74-4380-a21a-00ba35629f7d',
- 'bidder_code': 'bridgewell',
- 'cpm': 5.0,
- 'width': 728,
- 'height': 90,
- 'ad': 'test 728x90
',
- 'ttl': 360,
- 'net_revenue': 'true',
- 'currency': 'NTD'
- }, {
- 'id': '0e4048d3-5c74-4380-a21a-00ba35629f7d',
- 'bidder_code': 'bridgewell',
- 'cpm': 5.0,
- 'width': 728,
- 'height': 90,
- 'ad': 'test 728x90
',
- 'ttl': 360,
- 'net_revenue': 'true',
- 'currency': 'NTD'
- }];
+ const serverResponses = [
+ {
+ 'id': 'e5b10774-32bf-4931-85ee-05095e8cff21',
+ 'bidder_code': 'bridgewell',
+ 'cpm': 5.0,
+ 'width': 300,
+ 'height': 250,
+ 'ad': 'test 300x250
',
+ 'ttl': 360,
+ 'netRevenue': true,
+ 'currency': 'NTD'
+ },
+ {
+ 'id': '0e4048d3-5c74-4380-a21a-00ba35629f7d',
+ 'bidder_code': 'bridgewell',
+ 'cpm': 5.0,
+ 'width': 728,
+ 'height': 90,
+ 'ad': 'test 728x90
',
+ 'ttl': 360,
+ 'netRevenue': true,
+ 'currency': 'NTD'
+ },
+ {
+ 'id': '8f12c646-3b87-4326-a837-c2a76999f168',
+ 'bidder_code': 'bridgewell',
+ 'cpm': 5.0,
+ 'width': 300,
+ 'height': 250,
+ 'ad': 'test 300x250
',
+ 'ttl': 360,
+ 'netRevenue': true,
+ 'currency': 'NTD'
+ },
+ {
+ 'id': '8f12c646-3b87-4326-a837-c2a76999f168',
+ 'bidder_code': 'bridgewell',
+ 'cpm': 5.0,
+ 'width': 300,
+ 'height': 250,
+ 'ad': 'test 300x250
',
+ 'ttl': 360,
+ 'netRevenue': true,
+ 'currency': 'NTD'
+ },
+ {
+ 'id': '0e4048d3-5c74-4380-a21a-00ba35629f7d',
+ 'bidder_code': 'bridgewell',
+ 'cpm': 5.0,
+ 'width': 728,
+ 'height': 90,
+ 'ad': 'test 728x90
',
+ 'ttl': 360,
+ 'netRevenue': true,
+ 'currency': 'NTD'
+ },
+ {
+ 'id': '0e4048d3-5c74-4380-a21a-00ba35629f7d',
+ 'bidder_code': 'bridgewell',
+ 'cpm': 5.0,
+ 'width': 728,
+ 'height': 90,
+ 'ad': 'test 728x90
',
+ 'ttl': 360,
+ 'netRevenue': true,
+ 'currency': 'NTD'
+ },
+ {
+ 'id': '0e4048d3-5c74-4380-a21a-00ba35629f7d',
+ 'bidder_code': 'bridgewell',
+ 'cpm': 5.0,
+ 'width': 728,
+ 'height': 90,
+ 'ad': 'test 728x90
',
+ 'ttl': 360,
+ 'netRevenue': true,
+ 'currency': 'NTD'
+ }
+ ];
it('should return all required parameters', () => {
const result = spec.interpretResponse({'body': serverResponses}, request);
@@ -278,38 +313,114 @@ describe('bridgewellBidAdapter', function () {
expect(result).to.deep.equal([]);
});
- it('should give up bid if cpm is missing', () => {
+ it('should give up bid if request sizes is missing', () => {
let target = Object.assign({}, serverResponses[0]);
- delete target.cpm;
+ target.consumed = false;
+ const result = spec.interpretResponse({'body': [target]}, spec.buildRequests([{
+ 'bidder': 'bridgewell',
+ 'params': {
+ 'ChannelID': 'CLJgEAYYvxUiBXBlbm55KgkIrAIQ-gEaATk'
+ },
+ 'adUnitCode': 'adunit-code-1',
+ 'bidId': '30b31c1838de1e',
+ 'bidderRequestId': '22edbae2733bf6',
+ 'auctionId': '1d1a030790a475',
+ }]));
+ expect(result).to.deep.equal([]);
+ });
+
+ it('should give up bid if response sizes is invalid', () => {
+ let target = {
+ 'id': 'e5b10774-32bf-4931-85ee-05095e8cff21',
+ 'bidder_code': 'bridgewell',
+ 'cpm': 5.0,
+ 'width': 1,
+ 'height': 1,
+ 'ad': 'test 300x250
',
+ 'ttl': 360,
+ 'netRevenue': true,
+ 'currency': 'NTD'
+ };
+
+ const result = spec.interpretResponse({'body': [target]}, request);
+ expect(result).to.deep.equal([]);
+ });
+
+ it('should give up bid if cpm is missing', () => {
+ let target = {
+ 'id': 'e5b10774-32bf-4931-85ee-05095e8cff21',
+ 'bidder_code': 'bridgewell',
+ 'width': 300,
+ 'height': 250,
+ 'ad': 'test 300x250
',
+ 'ttl': 360,
+ 'netRevenue': true,
+ 'currency': 'NTD'
+ };
+
const result = spec.interpretResponse({'body': [target]}, request);
expect(result).to.deep.equal([]);
});
it('should give up bid if width or height is missing', () => {
- let target = Object.assign({}, serverResponses[0]);
- delete target.height;
- delete target.width;
+ let target = {
+ 'id': 'e5b10774-32bf-4931-85ee-05095e8cff21',
+ 'bidder_code': 'bridgewell',
+ 'cpm': 5.0,
+ 'ad': 'test 300x250
',
+ 'ttl': 360,
+ 'netRevenue': true,
+ 'currency': 'NTD'
+ };
+
const result = spec.interpretResponse({'body': [target]}, request);
expect(result).to.deep.equal([]);
});
it('should give up bid if ad is missing', () => {
- let target = Object.assign({}, serverResponses[0]);
- delete target.ad;
+ let target = {
+ 'id': 'e5b10774-32bf-4931-85ee-05095e8cff21',
+ 'bidder_code': 'bridgewell',
+ 'cpm': 5.0,
+ 'width': 300,
+ 'height': 250,
+ 'ttl': 360,
+ 'netRevenue': true,
+ 'currency': 'NTD'
+ };
+
const result = spec.interpretResponse({'body': [target]}, request);
expect(result).to.deep.equal([]);
});
it('should give up bid if revenue mode is missing', () => {
- let target = Object.assign({}, serverResponses[0]);
- delete target.net_revenue;
+ let target = {
+ 'id': 'e5b10774-32bf-4931-85ee-05095e8cff21',
+ 'bidder_code': 'bridgewell',
+ 'cpm': 5.0,
+ 'width': 300,
+ 'height': 250,
+ 'ad': 'test 300x250
',
+ 'ttl': 360,
+ 'currency': 'NTD'
+ };
+
const result = spec.interpretResponse({'body': [target]}, request);
expect(result).to.deep.equal([]);
});
it('should give up bid if currency is missing', () => {
- let target = Object.assign({}, serverResponses[0]);
- delete target.currency;
+ let target = {
+ 'id': 'e5b10774-32bf-4931-85ee-05095e8cff21',
+ 'bidder_code': 'bridgewell',
+ 'cpm': 5.0,
+ 'width': 300,
+ 'height': 250,
+ 'ad': 'test 300x250
',
+ 'ttl': 360,
+ 'netRevenue': true
+ };
+
const result = spec.interpretResponse({'body': [target]}, request);
expect(result).to.deep.equal([]);
});
diff --git a/test/spec/modules/clickforceBidAdapter_spec.js b/test/spec/modules/clickforceBidAdapter_spec.js
new file mode 100644
index 000000000000..8b0955590a01
--- /dev/null
+++ b/test/spec/modules/clickforceBidAdapter_spec.js
@@ -0,0 +1,127 @@
+import { expect } from 'chai';
+import { spec } from 'modules/clickforceBidAdapter';
+import { newBidder } from 'src/adapters/bidderFactory';
+
+describe('ClickforceAdapter', () => {
+ const adapter = newBidder(spec);
+
+ describe('inherited functions', () => {
+ it('exists and is a function', () => {
+ expect(adapter.callBids).to.exist.and.to.be.a('function');
+ });
+ });
+
+ describe('isBidRequestValid', () => {
+ let bid = {
+ 'bidder': 'clickforce',
+ 'params': {
+ 'zone': '6682'
+ },
+ 'adUnitCode': 'adunit-code',
+ 'sizes': [
+ [300, 250]
+ ],
+ 'bidId': '30b31c1838de1e',
+ 'bidderRequestId': '22edbae2733bf6',
+ 'auctionId': '1d1a030790a475'
+ };
+
+ it('should return true when required params found', () => {
+ expect(spec.isBidRequestValid(bid)).to.equal(true);
+ });
+
+ it('should return false when required params are not passed', () => {
+ let bid = Object.assign({}, bid);
+ delete bid.params;
+ bid.params = {
+ 'someIncorrectParam': 0
+ };
+ expect(spec.isBidRequestValid(bid)).to.equal(false);
+ });
+ });
+
+ describe('buildRequests', () => {
+ let bidRequests = [{
+ 'bidder': 'clickforce',
+ 'params': {
+ 'zone': '6682'
+ },
+ 'adUnitCode': 'adunit-code',
+ 'sizes': [
+ [300, 250]
+ ],
+ 'bidId': '30b31c1838de1e',
+ 'bidderRequestId': '22edbae2733bf6',
+ 'auctionId': '1d1a030790a475'
+ }];
+
+ const request = spec.buildRequests(bidRequests);
+
+ it('sends bid request to our endpoint via POST', () => {
+ expect(request.method).to.equal('POST');
+ });
+ });
+
+ describe('interpretResponse', () => {
+ let response = [{
+ 'cpm': 0.5,
+ 'width': '300',
+ 'height': '250',
+ 'callback_uid': '220ed41385952a',
+ 'type': 'Default Ad',
+ 'tag': '',
+ 'creativeId': '1f99ac5c3ef10a4097499a5686b30aff-6682',
+ 'requestId': '220ed41385952a',
+ 'currency': 'USD',
+ 'ttl': 60,
+ 'netRevenue': true,
+ 'zone': '6682'
+ }];
+
+ it('should get the correct bid response', () => {
+ let expectedResponse = [{
+ 'requestId': '220ed41385952a',
+ 'cpm': 0.5,
+ 'width': '300',
+ 'height': '250',
+ 'creativeId': '1f99ac5c3ef10a4097499a5686b30aff-6682',
+ 'currency': 'USD',
+ 'netRevenue': true,
+ 'ttl': 60,
+ 'ad': ''
+ }];
+
+ let bidderRequest;
+ let result = spec.interpretResponse({ body: response }, {bidderRequest});
+ expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0]));
+ });
+
+ it('handles empty bid response', () => {
+ let response = {
+ body: {}
+ };
+ let result = spec.interpretResponse(response);
+ expect(result.length).to.equal(0);
+ });
+ });
+
+ describe('getUserSyncs function', () => {
+ it('should register type is iframe', () => {
+ const syncOptions = {
+ 'iframeEnabled': 'true'
+ }
+ let userSync = spec.getUserSyncs(syncOptions);
+ expect(userSync[0].type).to.equal('iframe');
+ expect(userSync[0].url).to.equal('https://cdn.doublemax.net/js/capmapping.htm');
+ });
+
+ it('should register type is image', () => {
+ const syncOptions = {
+ 'pixelEnabled': 'true'
+ }
+ let userSync = spec.getUserSyncs(syncOptions);
+ expect(userSync[0].type).to.equal('image');
+ expect(userSync[0].url).to.equal('https://c.doublemax.net/cm');
+ });
+ });
+});
diff --git a/test/spec/modules/colossussspBidAdapter_spec.js b/test/spec/modules/colossussspBidAdapter_spec.js
index e14d2f27b421..54952fbf4b58 100644
--- a/test/spec/modules/colossussspBidAdapter_spec.js
+++ b/test/spec/modules/colossussspBidAdapter_spec.js
@@ -16,17 +16,13 @@ describe('ColossussspAdapter', () => {
};
describe('isBidRequestValid', () => {
- it('Should return true when placement_id can be cast to a number, and when at least one of the sizes passed is allowed', () => {
+ it('Should return true when placement_id can be cast to a number', () => {
expect(spec.isBidRequestValid(bid)).to.be.true;
});
it('Should return false when placement_id is not a number', () => {
bid.params.placement_id = 'aaa';
expect(spec.isBidRequestValid(bid)).to.be.false;
});
- it('Should return false when the sizes are not allowed', () => {
- bid.sizes = [[1, 1]];
- expect(spec.isBidRequestValid(bid)).to.be.false;
- });
});
describe('buildRequests', () => {
@@ -56,9 +52,10 @@ describe('ColossussspAdapter', () => {
let placements = data['placements'];
for (let i = 0; i < placements.length; i++) {
let placement = placements[i];
- expect(placement).to.have.all.keys('placementId', 'bidId', 'sizes');
+ expect(placement).to.have.all.keys('placementId', 'bidId', 'traffic', 'sizes');
expect(placement.placementId).to.be.a('number');
expect(placement.bidId).to.be.a('string');
+ expect(placement.traffic).to.be.a('string');
expect(placement.sizes).to.be.an('array');
}
});
@@ -72,6 +69,7 @@ describe('ColossussspAdapter', () => {
let resObject = {
body: [ {
requestId: '123',
+ mediaType: 'banner',
cpm: 0.3,
width: 320,
height: 50,
@@ -88,7 +86,7 @@ describe('ColossussspAdapter', () => {
for (let i = 0; i < serverResponses.length; i++) {
let dataItem = serverResponses[i];
expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId',
- 'netRevenue', 'currency');
+ 'netRevenue', 'currency', 'mediaType');
expect(dataItem.requestId).to.be.a('string');
expect(dataItem.cpm).to.be.a('number');
expect(dataItem.width).to.be.a('number');
@@ -98,6 +96,7 @@ describe('ColossussspAdapter', () => {
expect(dataItem.creativeId).to.be.a('string');
expect(dataItem.netRevenue).to.be.a('boolean');
expect(dataItem.currency).to.be.a('string');
+ expect(dataItem.mediaType).to.be.a('string');
}
it('Returns an empty array if invalid response is passed', () => {
serverResponses = spec.interpretResponse('invalid_response');
diff --git a/test/spec/modules/consentManagement_spec.js b/test/spec/modules/consentManagement_spec.js
new file mode 100644
index 000000000000..5974ac79324e
--- /dev/null
+++ b/test/spec/modules/consentManagement_spec.js
@@ -0,0 +1,292 @@
+import {setConfig, requestBidsHook, resetConsentData, userCMP, consentTimeout, allowAuction} from 'modules/consentManagement';
+import {gdprDataHandler} from 'src/adaptermanager';
+import * as utils from 'src/utils';
+import { config } from 'src/config';
+
+let assert = require('chai').assert;
+let expect = require('chai').expect;
+
+describe('consentManagement', function () {
+ describe('setConfig tests:', () => {
+ describe('empty setConfig value', () => {
+ beforeEach(() => {
+ sinon.stub(utils, 'logInfo');
+ });
+
+ afterEach(() => {
+ utils.logInfo.restore();
+ config.resetConfig();
+ });
+
+ it('should use system default values', () => {
+ setConfig({});
+ expect(userCMP).to.be.equal('iab');
+ expect(consentTimeout).to.be.equal(10000);
+ expect(allowAuction).to.be.true;
+ sinon.assert.callCount(utils.logInfo, 3);
+ });
+ });
+
+ describe('valid setConfig value', () => {
+ afterEach(() => {
+ config.resetConfig();
+ $$PREBID_GLOBAL$$.requestBids.removeHook(requestBidsHook);
+ });
+ it('results in all user settings overriding system defaults', () => {
+ let allConfig = {
+ cmpApi: 'iab',
+ timeout: 7500,
+ allowAuctionWithoutConsent: false
+ };
+
+ setConfig(allConfig);
+ expect(userCMP).to.be.equal('iab');
+ expect(consentTimeout).to.be.equal(7500);
+ expect(allowAuction).to.be.false;
+ });
+ });
+ });
+
+ describe('requestBidsHook tests:', () => {
+ let goodConfigWithCancelAuction = {
+ cmpApi: 'iab',
+ timeout: 7500,
+ allowAuctionWithoutConsent: false
+ };
+
+ let goodConfigWithAllowAuction = {
+ cmpApi: 'iab',
+ timeout: 7500,
+ allowAuctionWithoutConsent: true
+ };
+
+ let didHookReturn;
+
+ afterEach(() => {
+ gdprDataHandler.consentData = null;
+ resetConsentData();
+ });
+
+ describe('error checks:', () => {
+ describe('unknown CMP framework ID:', () => {
+ beforeEach(() => {
+ sinon.stub(utils, 'logWarn');
+ });
+
+ afterEach(() => {
+ utils.logWarn.restore();
+ config.resetConfig();
+ $$PREBID_GLOBAL$$.requestBids.removeHook(requestBidsHook);
+ gdprDataHandler.consentData = null;
+ });
+
+ it('should return Warning message and return to hooked function', () => {
+ let badCMPConfig = {
+ cmpApi: 'bad'
+ };
+ setConfig(badCMPConfig);
+ expect(userCMP).to.be.equal(badCMPConfig.cmpApi);
+
+ didHookReturn = false;
+
+ requestBidsHook({}, () => {
+ didHookReturn = true;
+ });
+ let consent = gdprDataHandler.getConsentData();
+ sinon.assert.calledOnce(utils.logWarn);
+ expect(didHookReturn).to.be.true;
+ expect(consent).to.be.null;
+ });
+ });
+ });
+
+ describe('already known consentData:', () => {
+ let cmpStub = sinon.stub();
+
+ beforeEach(() => {
+ didHookReturn = false;
+ window.__cmp = function() {};
+ });
+
+ afterEach(() => {
+ config.resetConfig();
+ $$PREBID_GLOBAL$$.requestBids.removeHook(requestBidsHook);
+ cmpStub.restore();
+ delete window.__cmp;
+ gdprDataHandler.consentData = null;
+ });
+
+ it('should bypass CMP and simply use previously stored consentData', () => {
+ let testConsentData = {
+ gdprApplies: true,
+ metadata: 'xyz'
+ };
+
+ cmpStub = sinon.stub(window, '__cmp').callsFake((...args) => {
+ args[2](testConsentData);
+ });
+ setConfig(goodConfigWithAllowAuction);
+ requestBidsHook({}, () => {});
+ cmpStub.restore();
+
+ // reset the stub to ensure it wasn't called during the second round of calls
+ cmpStub = sinon.stub(window, '__cmp').callsFake((...args) => {
+ args[2](testConsentData);
+ });
+
+ requestBidsHook({}, () => {
+ didHookReturn = true;
+ });
+ let consent = gdprDataHandler.getConsentData();
+
+ expect(didHookReturn).to.be.true;
+ expect(consent.consentString).to.equal(testConsentData.metadata);
+ expect(consent.gdprApplies).to.be.true;
+ sinon.assert.notCalled(cmpStub);
+ });
+ });
+
+ describe('CMP workflow for iframed page', () => {
+ let eventStub = sinon.stub();
+ let cmpStub = sinon.stub();
+
+ beforeEach(() => {
+ didHookReturn = false;
+ resetConsentData();
+ window.__cmp = function() {};
+ sinon.stub(utils, 'logError');
+ sinon.stub(utils, 'logWarn');
+ });
+
+ afterEach(() => {
+ config.resetConfig();
+ $$PREBID_GLOBAL$$.requestBids.removeHook(requestBidsHook);
+ eventStub.restore();
+ cmpStub.restore();
+ delete window.__cmp;
+ utils.logError.restore();
+ utils.logWarn.restore();
+ gdprDataHandler.consentData = null;
+ });
+
+ it('should return the consent string from a postmessage + addEventListener response', () => {
+ let testConsentData = {
+ data: {
+ __cmpReturn: {
+ returnValue: {
+ gdprApplies: true,
+ metadata: 'BOJy+UqOJy+UqABAB+AAAAAZ+A=='
+ }
+ }
+ }
+ };
+ eventStub = sinon.stub(window, 'addEventListener').callsFake((...args) => {
+ args[1](testConsentData);
+ });
+ cmpStub = sinon.stub(window, '__cmp').callsFake((...args) => {
+ args[2]({
+ gdprApplies: true,
+ metadata: 'BOJy+UqOJy+UqABAB+AAAAAZ+A=='
+ });
+ });
+
+ setConfig(goodConfigWithAllowAuction);
+
+ requestBidsHook({}, () => {
+ didHookReturn = true;
+ });
+ let consent = gdprDataHandler.getConsentData();
+
+ sinon.assert.notCalled(utils.logWarn);
+ sinon.assert.notCalled(utils.logError);
+ expect(didHookReturn).to.be.true;
+ expect(consent.consentString).to.equal('BOJy+UqOJy+UqABAB+AAAAAZ+A==');
+ expect(consent.gdprApplies).to.be.true;
+ });
+ });
+
+ describe('CMP workflow for normal pages:', () => {
+ let cmpStub = sinon.stub();
+
+ beforeEach(() => {
+ didHookReturn = false;
+ resetConsentData();
+ sinon.stub(utils, 'logError');
+ sinon.stub(utils, 'logWarn');
+ window.__cmp = function() {};
+ });
+
+ afterEach(() => {
+ config.resetConfig();
+ $$PREBID_GLOBAL$$.requestBids.removeHook(requestBidsHook);
+ cmpStub.restore();
+ utils.logError.restore();
+ utils.logWarn.restore();
+ delete window.__cmp;
+ gdprDataHandler.consentData = null;
+ });
+
+ it('performs lookup check and stores consentData for a valid existing user', () => {
+ let testConsentData = {
+ gdprApplies: true,
+ metadata: 'BOJy+UqOJy+UqABAB+AAAAAZ+A=='
+ };
+ cmpStub = sinon.stub(window, '__cmp').callsFake((...args) => {
+ args[2](testConsentData);
+ });
+
+ setConfig(goodConfigWithAllowAuction);
+
+ requestBidsHook({}, () => {
+ didHookReturn = true;
+ });
+ let consent = gdprDataHandler.getConsentData();
+
+ sinon.assert.notCalled(utils.logWarn);
+ sinon.assert.notCalled(utils.logError);
+ expect(didHookReturn).to.be.true;
+ expect(consent.consentString).to.equal(testConsentData.metadata);
+ expect(consent.gdprApplies).to.be.true;
+ });
+
+ it('throws an error when processCmpData check failed while config had allowAuction set to false', () => {
+ let testConsentData = null;
+
+ cmpStub = sinon.stub(window, '__cmp').callsFake((...args) => {
+ args[2](testConsentData);
+ });
+
+ setConfig(goodConfigWithCancelAuction);
+
+ requestBidsHook({}, () => {
+ didHookReturn = true;
+ });
+ let consent = gdprDataHandler.getConsentData();
+
+ sinon.assert.calledOnce(utils.logError);
+ expect(didHookReturn).to.be.false;
+ expect(consent).to.be.null;
+ });
+
+ it('throws a warning + stores consentData + calls callback when processCmpData check failed while config had allowAuction set to true', () => {
+ let testConsentData = null;
+
+ cmpStub = sinon.stub(window, '__cmp').callsFake((...args) => {
+ args[2](testConsentData);
+ });
+
+ setConfig(goodConfigWithAllowAuction);
+
+ requestBidsHook({}, () => {
+ didHookReturn = true;
+ });
+ let consent = gdprDataHandler.getConsentData();
+
+ sinon.assert.calledOnce(utils.logWarn);
+ expect(didHookReturn).to.be.true;
+ expect(consent.consentString).to.be.undefined;
+ expect(consent.gdprApplies).to.be.undefined;
+ });
+ });
+ });
+});
diff --git a/test/spec/modules/consumableBidAdapter_spec.js b/test/spec/modules/consumableBidAdapter_spec.js
new file mode 100644
index 000000000000..b87ce6634f65
--- /dev/null
+++ b/test/spec/modules/consumableBidAdapter_spec.js
@@ -0,0 +1,215 @@
+import {expect} from 'chai';
+import * as utils from 'src/utils';
+import {spec} from 'modules/consumableBidAdapter';
+import {config} from 'src/config';
+
+const DEFAULT_OAD_CONTENT = '';
+const DEFAULT_AD_CONTENT = ''
+
+let getDefaultBidResponse = () => {
+ return {
+ id: '245730051428950632',
+ cur: 'USD',
+ seatbid: [{
+ bid: [{
+ id: 1,
+ impid: '245730051428950632',
+ price: 0.09,
+ adm: DEFAULT_OAD_CONTENT,
+ crid: 'creative-id',
+ h: 90,
+ w: 728,
+ dealid: 'deal-id',
+ ext: {sizeid: 225}
+ }]
+ }]
+ };
+};
+
+let getBidParams = () => {
+ return {
+ placement: 1234567,
+ network: '9599.1',
+ unitId: '987654',
+ unitName: 'unitname',
+ zoneId: '9599.1'
+ };
+};
+
+let getDefaultBidRequest = () => {
+ return {
+ bidderCode: 'consumable',
+ auctionId: 'd3e07445-ab06-44c8-a9dd-5ef9af06d2a6',
+ bidderRequestId: '7101db09af0db2',
+ start: new Date().getTime(),
+ bids: [{
+ bidder: 'consumable',
+ bidId: '84ab500420319d',
+ bidderRequestId: '7101db09af0db2',
+ auctionId: 'd3e07445-ab06-44c8-a9dd-5ef9af06d2a6',
+ placementCode: 'foo',
+ params: getBidParams()
+ }]
+ };
+};
+
+let getPixels = () => {
+ return '';
+};
+
+describe('ConsumableAdapter', () => {
+ const CONSUMABLE_URL = '//adserver-us.adtech.advertising.com/pubapi/3.0/';
+ const CONSUMABLE_TTL = 60;
+
+ function createCustomBidRequest({bids, params} = {}) {
+ var bidderRequest = getDefaultBidRequest();
+ if (bids && Array.isArray(bids)) {
+ bidderRequest.bids = bids;
+ }
+ if (params) {
+ bidderRequest.bids.forEach(bid => bid.params = params);
+ }
+ return bidderRequest;
+ }
+
+ describe('interpretResponse()', () => {
+ let bidderSettingsBackup;
+ let bidResponse;
+ let bidRequest;
+ let logWarnSpy;
+
+ beforeEach(() => {
+ bidderSettingsBackup = $$PREBID_GLOBAL$$.bidderSettings;
+ bidRequest = {
+ bidderCode: 'test-bidder-code',
+ bidId: 'bid-id',
+ unitName: 'unitname',
+ unitId: '987654',
+ zoneId: '9599.1',
+ network: '9599.1'
+ };
+ bidResponse = {
+ body: getDefaultBidResponse()
+ };
+ logWarnSpy = sinon.spy(utils, 'logWarn');
+ });
+
+ afterEach(() => {
+ $$PREBID_GLOBAL$$.bidderSettings = bidderSettingsBackup;
+ logWarnSpy.restore();
+ });
+
+ it('should return formatted bid response with required properties', () => {
+ let formattedBidResponse = spec.interpretResponse(bidResponse, bidRequest);
+ expect(formattedBidResponse).to.deep.equal({
+ bidderCode: bidRequest.bidderCode,
+ requestId: 'bid-id',
+ ad: DEFAULT_AD_CONTENT,
+ cpm: 0.09,
+ width: 728,
+ height: 90,
+ creativeId: 'creative-id',
+ pubapiId: '245730051428950632',
+ currency: 'USD',
+ dealId: 'deal-id',
+ netRevenue: true,
+ ttl: 60
+ });
+ });
+
+ it('should add formatted pixels to ad content when pixels are present in the response', () => {
+ bidResponse.body.ext = {
+ pixels: 'pixels-content'
+ };
+
+ let formattedBidResponse = spec.interpretResponse(bidResponse, bidRequest);
+
+ expect(formattedBidResponse.ad).to.equal(DEFAULT_AD_CONTENT + '');
+ return true;
+ });
+ });
+
+ describe('buildRequests()', () => {
+ it('method exists and is a function', () => {
+ expect(spec.buildRequests).to.exist.and.to.be.a('function');
+ });
+
+ describe('Consumable', () => {
+ it('should not return request when no bids are present', () => {
+ let [request] = spec.buildRequests([]);
+ expect(request).to.be.empty;
+ });
+
+ it('should return request for endpoint', () => {
+ let bidRequest = getDefaultBidRequest();
+ let [request] = spec.buildRequests(bidRequest.bids);
+ expect(request.url).to.contain(CONSUMABLE_URL);
+ });
+
+ it('should return url with pubapi bid option', () => {
+ let bidRequest = getDefaultBidRequest();
+ let [request] = spec.buildRequests(bidRequest.bids);
+ expect(request.url).to.contain('cmd=bid;');
+ });
+
+ it('should return url with version 2 of pubapi', () => {
+ let bidRequest = getDefaultBidRequest();
+ let [request] = spec.buildRequests(bidRequest.bids);
+ expect(request.url).to.contain('v=2;');
+ });
+
+ it('should return url with cache busting option', () => {
+ let bidRequest = getDefaultBidRequest();
+ let [request] = spec.buildRequests(bidRequest.bids);
+ expect(request.url).to.match(/misc=\d+/);
+ });
+ });
+ });
+
+ describe('getUserSyncs()', () => {
+ let bidResponse;
+ let bidRequest;
+
+ beforeEach(() => {
+ $$PREBID_GLOBAL$$.consumableGlobals.pixelsDropped = false;
+ config.setConfig({
+ consumable: {
+ userSyncOn: 'bidResponse'
+ },
+ });
+ bidResponse = getDefaultBidResponse();
+ bidResponse.ext = {
+ pixels: getPixels()
+ };
+ });
+
+ it('should return user syncs only if userSyncOn equals to "bidResponse"', () => {
+ let userSyncs = spec.getUserSyncs({}, [bidResponse], bidRequest);
+
+ expect($$PREBID_GLOBAL$$.consumableGlobals.pixelsDropped).to.be.true;
+ expect(userSyncs).to.deep.equal([
+ {type: 'image', url: 'img.org'},
+ {type: 'iframe', url: 'pixels1.org'}
+ ]);
+ });
+
+ it('should not return user syncs if it has already been returned', () => {
+ $$PREBID_GLOBAL$$.consumableGlobals.pixelsDropped = true;
+
+ let userSyncs = spec.getUserSyncs({}, [bidResponse], bidRequest);
+
+ expect($$PREBID_GLOBAL$$.consumableGlobals.pixelsDropped).to.be.true;
+ expect(userSyncs).to.deep.equal([]);
+ });
+
+ it('should not return user syncs if pixels are not present', () => {
+ bidResponse.ext.pixels = null;
+
+ let userSyncs = spec.getUserSyncs({}, [bidResponse], bidRequest);
+
+ expect($$PREBID_GLOBAL$$.consumableGlobals.pixelsDropped).to.be.false;
+ expect(userSyncs).to.deep.equal([]);
+ });
+ });
+});
diff --git a/test/spec/modules/dgadsBidAdapter_spec.js b/test/spec/modules/dgadsBidAdapter_spec.js
new file mode 100644
index 000000000000..89affd94880b
--- /dev/null
+++ b/test/spec/modules/dgadsBidAdapter_spec.js
@@ -0,0 +1,291 @@
+import {expect} from 'chai';
+import * as utils from 'src/utils';
+import {spec} from 'modules/dgadsBidAdapter';
+import {newBidder} from 'src/adapters/bidderFactory';
+import { BANNER, NATIVE } from 'src/mediaTypes';
+
+describe('dgadsBidAdapter', () => {
+ const adapter = newBidder(spec);
+ const VALID_ENDPOINT = 'https://ads-tr.bigmining.com/ad/p/bid';
+
+ describe('inherited functions', () => {
+ it('exists and is a function', () => {
+ expect(adapter.callBids).to.exist.and.to.be.a('function');
+ });
+ });
+
+ describe('isBidRequestValid', () => {
+ let bid = {
+ 'bidder': 'dgads',
+ params: {
+ site_id: '1',
+ location_id: '1'
+ }
+ };
+ it('should return true when required params found', () => {
+ expect(spec.isBidRequestValid(bid)).to.equal(true);
+ });
+
+ it('should return false when required params(location_id) are not passed', () => {
+ let bid = Object.assign({}, bid);
+ delete bid.params;
+ bid.params = {
+ site_id: '1'
+ };
+ expect(spec.isBidRequestValid(bid)).to.equal(false);
+ });
+
+ it('should return false when required params(site_id) are not passed', () => {
+ let bid = Object.assign({}, bid);
+ delete bid.params;
+ bid.params = {
+ location_id: '1'
+ };
+ expect(spec.isBidRequestValid(bid)).to.equal(false);
+ });
+ });
+
+ describe('buildRequests', () => {
+ const bidRequests = [
+ { // banner
+ bidder: 'dgads',
+ mediaType: 'banner',
+ params: {
+ site_id: '1',
+ location_id: '1'
+ },
+ adUnitCode: 'adunit-code',
+ sizes: [[300, 250]],
+ bidId: '2db3101abaec66',
+ bidderRequestId: '14a9f773e30243',
+ auctionId: 'c0cd37c5-af11-464d-b83e-35863e533b1f',
+ transactionId: 'c1f1eff6-23c6-4844-a321-575212939e37'
+ },
+ { // native
+ bidder: 'dgads',
+ sizes: [[300, 250]],
+ params: {
+ site_id: '1',
+ location_id: '10'
+ },
+ mediaTypes: {
+ native: {
+ image: {
+ required: true
+ },
+ title: {
+ required: true,
+ len: 25
+ },
+ clickUrl: {
+ required: true
+ },
+ body: {
+ required: true,
+ len: 140
+ },
+ sponsoredBy: {
+ required: true,
+ len: 40
+ }
+ },
+ },
+ adUnitCode: 'adunit-code',
+ bidId: '2db3101abaec66',
+ bidderRequestId: '14a9f773e30243',
+ auctionId: 'c0cd37c5-af11-464d-b83e-35863e533b1f',
+ transactionId: 'c1f1eff6-23c6-4844-a321-575212939e37'
+ }
+ ];
+ it('no bidRequests', () => {
+ const noBidRequests = [];
+ expect(Object.keys(spec.buildRequests(noBidRequests)).length).to.equal(0);
+ });
+ const data = {
+ location_id: '1',
+ site_id: '1',
+ transaction_id: 'c1f1eff6-23c6-4844-a321-575212939e37',
+ bid_id: '2db3101abaec66'
+ };
+ it('sends bid request to VALID_ENDPOINT via POST', () => {
+ const request = spec.buildRequests(bidRequests)[0];
+ expect(request.url).to.equal(VALID_ENDPOINT);
+ expect(request.method).to.equal('POST');
+ });
+ it('should attache params to the request', () => {
+ const request = spec.buildRequests(bidRequests)[0];
+ expect(request.data['location_id']).to.equal(data['location_id']);
+ expect(request.data['site_id']).to.equal(data['site_id']);
+ expect(request.data['transaction_id']).to.equal(data['transaction_id']);
+ expect(request.data['bid_id']).to.equal(data['bid_id']);
+ });
+ });
+
+ describe('interpretResponse', () => {
+ const bidRequests = {
+ banner: {
+ bidRequest: {
+ bidder: 'dgads',
+ params: {
+ location_id: '1',
+ site_id: '1'
+ },
+ transactionId: 'c1f1eff6-23c6-4844-a321-575212939e37',
+ bidId: '2db3101abaec66',
+ adUnitCode: 'adunit-code',
+ sizes: [[300, 250]],
+ bidderRequestId: '14a9f773e30243',
+ auctionId: 'c0cd37c5-af11-464d-b83e-35863e533b1f'
+ },
+ },
+ native: {
+ bidRequest: {
+ bidder: 'adg',
+ params: {
+ site_id: '1',
+ location_id: '10'
+ },
+ mediaTypes: {
+ native: {
+ image: {
+ required: true
+ },
+ title: {
+ required: true,
+ len: 25
+ },
+ body: {
+ required: true,
+ len: 140
+ },
+ sponsoredBy: {
+ required: true,
+ len: 40
+ }
+ }
+ },
+ transactionId: 'f76f6dfd-d64f-4645-a29f-682bac7f431a',
+ bidId: '2f6ac468a9c15e',
+ adUnitCode: 'adunit-code',
+ sizes: [[1, 1]],
+ bidderRequestId: '14a9f773e30243',
+ auctionId: '4aae9f05-18c6-4fcd-80cf-282708cd584a',
+ },
+ },
+ };
+
+ const serverResponse = {
+ noAd: {
+ results: [],
+ },
+ banner: {
+ bids: {
+ ads: {
+ ad: '
',
+ cpm: 1.22,
+ w: 300,
+ h: 250,
+ creativeId: 'xuidx62944aab4fx37f',
+ ttl: 60,
+ bidId: '2f6ac468a9c15e'
+ }
+ }
+ },
+ native: {
+ bids: {
+ ads: {
+ cpm: 1.22,
+ title: 'title',
+ desc: 'description',
+ sponsoredBy: 'sponsoredBy',
+ image: 'https://ads-tr.bigmining.com/img/300_250_1.jpg',
+ w: 300,
+ h: 250,
+ ttl: 60,
+ bidId: '2f6ac468a9c15e',
+ creativeId: 'xuidx62944aab4fx37f',
+ isNative: 1,
+ impressionTrackers: ['https://ads-tr.bigmining.com/ad/view/beacon.gif'],
+ clickTrackers: ['https://ads-tr.bigmining.com/ad/view/beacon.png'],
+ clickUrl: 'http://www.garage.co.jp/ja/'
+ },
+ }
+ }
+ };
+
+ const bidResponses = {
+ banner: {
+ requestId: '2f6ac468a9c15e',
+ cpm: 1.22,
+ width: 300,
+ height: 250,
+ creativeId: 'xuidx62944aab4fx37f',
+ currency: 'JPY',
+ netRevenue: true,
+ ttl: 60,
+ referrer: utils.getTopWindowUrl(),
+ ad: '
',
+ },
+ native: {
+ requestId: '2f6ac468a9c15e',
+ cpm: 1.22,
+ creativeId: 'xuidx62944aab4fx37f',
+ currency: 'JPY',
+ netRevenue: true,
+ ttl: 60,
+ native: {
+ image: {
+ url: 'https://ads-tr.bigmining.com/img/300_250_1.jpg',
+ width: 300,
+ height: 250
+ },
+ title: 'title',
+ body: 'description',
+ sponsoredBy: 'sponsoredBy',
+ clickUrl: 'http://www.garage.co.jp/ja/',
+ impressionTrackers: ['https://ads-tr.bigmining.com/ad/view/beacon.gif'],
+ clickTrackers: ['https://ads-tr.bigmining.com/ad/view/beacon.png']
+ },
+ referrer: utils.getTopWindowUrl(),
+ creativeid: 'xuidx62944aab4fx37f',
+ mediaType: NATIVE
+ }
+ };
+
+ it('no bid responses', () => {
+ const result = spec.interpretResponse({body: serverResponse.noAd}, bidRequests.banner);
+ expect(result.length).to.equal(0);
+ });
+ it('handles banner responses', () => {
+ const result = spec.interpretResponse({body: serverResponse.banner}, bidRequests.banner)[0];
+ expect(result.requestId).to.equal(bidResponses.banner.requestId);
+ expect(result.width).to.equal(bidResponses.banner.width);
+ expect(result.height).to.equal(bidResponses.banner.height);
+ expect(result.creativeId).to.equal(bidResponses.banner.creativeId);
+ expect(result.currency).to.equal(bidResponses.banner.currency);
+ expect(result.netRevenue).to.equal(bidResponses.banner.netRevenue);
+ expect(result.ttl).to.equal(bidResponses.banner.ttl);
+ expect(result.referrer).to.equal(bidResponses.banner.referrer);
+ expect(result.ad).to.equal(bidResponses.banner.ad);
+ });
+
+ it('handles native responses', () => {
+ const result = spec.interpretResponse({body: serverResponse.native}, bidRequests.native)[0];
+ expect(result.requestId).to.equal(bidResponses.native.requestId);
+ expect(result.creativeId).to.equal(bidResponses.native.creativeId);
+ expect(result.currency).to.equal(bidResponses.native.currency);
+ expect(result.netRevenue).to.equal(bidResponses.native.netRevenue);
+ expect(result.ttl).to.equal(bidResponses.native.ttl);
+ expect(result.referrer).to.equal(bidResponses.native.referrer);
+ expect(result.native.title).to.equal(bidResponses.native.native.title);
+ expect(result.native.body).to.equal(bidResponses.native.native.body);
+ expect(result.native.sponsoredBy).to.equal(bidResponses.native.native.sponsoredBy);
+ expect(result.native.image.url).to.equal(bidResponses.native.native.image.url);
+ expect(result.native.image.width).to.equal(bidResponses.native.native.image.width);
+ expect(result.native.image.height).to.equal(bidResponses.native.native.image.height);
+ expect(result.native.clickUrl).to.equal(bidResponses.native.native.clickUrl);
+ expect(result.native.impressionTrackers[0]).to.equal(bidResponses.native.native.impressionTrackers[0]);
+ expect(result.native.clickTrackers[0]).to.equal(bidResponses.native.native.clickTrackers[0]);
+ });
+ });
+});
diff --git a/test/spec/modules/getintentBidAdapter_spec.js b/test/spec/modules/getintentBidAdapter_spec.js
index 1b76c4852b46..17f9a95fec48 100644
--- a/test/spec/modules/getintentBidAdapter_spec.js
+++ b/test/spec/modules/getintentBidAdapter_spec.js
@@ -9,7 +9,15 @@ describe('GetIntent Adapter Tests:', () => {
tid: 't1000'
},
sizes: [[300, 250]]
- }];
+ },
+ {
+ bidId: 'bid54321',
+ params: {
+ pid: 'p1000',
+ tid: 't1000'
+ },
+ sizes: [[50, 50], [100, 100]]
+ }]
const videoBidRequest = {
bidId: 'bid789',
params: {
@@ -36,6 +44,8 @@ describe('GetIntent Adapter Tests:', () => {
expect(serverRequest.data.tid).to.equal('t1000');
expect(serverRequest.data.size).to.equal('300x250');
expect(serverRequest.data.is_video).to.equal(false);
+ serverRequest = serverRequests[1];
+ expect(serverRequest.data.size).to.equal('50x50,100x100');
});
it('Verify build video request', () => {
@@ -123,6 +133,7 @@ describe('GetIntent Adapter Tests:', () => {
it('Verify if bid request valid', () => {
expect(spec.isBidRequestValid(bidRequests[0])).to.equal(true);
+ expect(spec.isBidRequestValid(bidRequests[1])).to.equal(true);
expect(spec.isBidRequestValid({})).to.equal(false);
expect(spec.isBidRequestValid({ params: {} })).to.equal(false);
expect(spec.isBidRequestValid({ params: { test: 123 } })).to.equal(false);
diff --git a/test/spec/modules/medianetBidAdapter_spec.js b/test/spec/modules/medianetBidAdapter_spec.js
index 36868e7f9931..520ec34fc7d9 100644
--- a/test/spec/modules/medianetBidAdapter_spec.js
+++ b/test/spec/modules/medianetBidAdapter_spec.js
@@ -69,6 +69,9 @@ let VALID_BID_REQUEST = [{
'bidderRequestId': '1e9b1f07797c1c',
'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d'
}],
+ VALID_AUCTIONDATA = {
+ 'timeout': config.getConfig('bidderTimeout'),
+ },
VALID_PAYLOAD_INVALID_BIDFLOOR = {
'site': {
'page': 'http://media.net/prebidtest',
@@ -166,6 +169,18 @@ let VALID_BID_REQUEST = [{
}],
'tmax': config.getConfig('bidderTimeout')
},
+ VALID_PAYLOAD_PAGE_META = (() => {
+ let PAGE_META;
+ try {
+ PAGE_META = JSON.parse(JSON.stringify(VALID_PAYLOAD));
+ } catch (e) {}
+ PAGE_META.site = Object.assign(PAGE_META.site, {
+ 'canonical_url': 'http://localhost:9999/canonical-test',
+ 'twitter_url': 'http://localhost:9999/twitter-test',
+ 'og_url': 'http://localhost:9999/fb-test'
+ });
+ return PAGE_META;
+ })(),
VALID_PARAMS = {
bidder: 'medianet',
params: {
@@ -343,19 +358,37 @@ describe('Media.net bid adapter', () => {
describe('buildRequests', () => {
it('should build valid payload on bid', () => {
- let requestObj = spec.buildRequests(VALID_BID_REQUEST);
+ let requestObj = spec.buildRequests(VALID_BID_REQUEST, VALID_AUCTIONDATA);
expect(JSON.parse(requestObj.data)).to.deep.equal(VALID_PAYLOAD);
});
it('should accept size as a one dimensional array', () => {
- let bidReq = spec.buildRequests(BID_REQUEST_SIZE_AS_1DARRAY);
+ let bidReq = spec.buildRequests(BID_REQUEST_SIZE_AS_1DARRAY, VALID_AUCTIONDATA);
expect(JSON.parse(bidReq.data)).to.deep.equal(VALID_PAYLOAD);
});
it('should ignore bidfloor if not a valid number', () => {
- let bidReq = spec.buildRequests(VALID_BID_REQUEST_INVALID_BIDFLOOR);
+ let bidReq = spec.buildRequests(VALID_BID_REQUEST_INVALID_BIDFLOOR, VALID_AUCTIONDATA);
expect(JSON.parse(bidReq.data)).to.deep.equal(VALID_PAYLOAD_INVALID_BIDFLOOR);
});
+ describe('build requests: when page meta-data is available', () => {
+ it('should pass canonical, twitter and fb paramters if available', () => {
+ let sandbox = sinon.sandbox.create();
+ let documentStub = sandbox.stub(window.top.document, 'querySelector');
+ documentStub.withArgs('link[rel="canonical"]').returns({
+ href: 'http://localhost:9999/canonical-test'
+ });
+ documentStub.withArgs('meta[property="og:url"]').returns({
+ content: 'http://localhost:9999/fb-test'
+ });
+ documentStub.withArgs('meta[name="twitter:url"]').returns({
+ content: 'http://localhost:9999/twitter-test'
+ });
+ let bidReq = spec.buildRequests(VALID_BID_REQUEST, VALID_AUCTIONDATA);
+ expect(JSON.parse(bidReq.data)).to.deep.equal(VALID_PAYLOAD_PAGE_META);
+ sandbox.restore();
+ });
+ });
});
describe('getUserSyncs', () => {
diff --git a/test/spec/modules/openxBidAdapter_spec.js b/test/spec/modules/openxBidAdapter_spec.js
index 1fb7e6f600bc..b763c111998f 100644
--- a/test/spec/modules/openxBidAdapter_spec.js
+++ b/test/spec/modules/openxBidAdapter_spec.js
@@ -2,6 +2,7 @@ import {expect} from 'chai';
import {spec} from 'modules/openxBidAdapter';
import {newBidder} from 'src/adapters/bidderFactory';
import {userSync} from 'src/userSync';
+import * as utils from 'src/utils';
const URLBASE = '/w/1.0/arj';
const URLBASEVIDEO = '/v/1.0/avjp';
@@ -9,6 +10,116 @@ const URLBASEVIDEO = '/v/1.0/avjp';
describe('OpenxAdapter', () => {
const adapter = newBidder(spec);
+ /**
+ * Type Definitions
+ */
+
+ /**
+ * @typedef {{
+ * impression: string,
+ * inview: string,
+ * click: string
+ * }}
+ */
+ let OxArjTracking;
+ /**
+ * @typedef {{
+ * ads: {
+ * version: number,
+ * count: number,
+ * pixels: string,
+ * ad: Array
+ * }
+ * }}
+ */
+ let OxArjResponse;
+ /**
+ * @typedef {{
+ * adunitid: number,
+ * adid:number,
+ * type: string,
+ * htmlz: string,
+ * framed: number,
+ * is_fallback: number,
+ * ts: string,
+ * cpipc: number,
+ * pub_rev: string,
+ * tbd: ?string,
+ * adv_id: string,
+ * deal_id: string,
+ * auct_win_is_deal: number,
+ * brand_id: string,
+ * currency: string,
+ * idx: string,
+ * creative: Array
+ * }}
+ */
+ let OxArjAdUnit;
+ /**
+ * @typedef {{
+ * id: string,
+ * width: string,
+ * height: string,
+ * target: string,
+ * mime: string,
+ * media: string,
+ * tracking: OxArjTracking
+ * }}
+ */
+ let OxArjCreative;
+
+ // HELPER METHODS
+ /**
+ * @type {OxArjCreative}
+ */
+ const DEFAULT_TEST_ARJ_CREATIVE = {
+ id: '0',
+ width: 'test-width',
+ height: 'test-height',
+ target: 'test-target',
+ mime: 'test-mime',
+ media: 'test-media',
+ tracking: {
+ impression: 'test-impression',
+ inview: 'test-inview',
+ click: 'test-click'
+ }
+ };
+
+ /**
+ * @type {OxArjAdUnit}
+ */
+ const DEFAULT_TEST_ARJ_AD_UNIT = {
+ adunitid: 0,
+ type: 'test-type',
+ html: 'test-html',
+ framed: 0,
+ is_fallback: 0,
+ ts: 'test-ts',
+ tbd: 'NaN',
+ deal_id: undefined,
+ auct_win_is_deal: undefined,
+ cpipc: 0,
+ pub_rev: 'test-pub_rev',
+ adv_id: 'test-adv_id',
+ brand_id: 'test-brand_id',
+ currency: 'test-currency',
+ idx: '0',
+ creative: [DEFAULT_TEST_ARJ_CREATIVE]
+ };
+
+ /**
+ * @type {OxArjResponse}
+ */
+ const DEFAULT_ARJ_RESPONSE = {
+ ads: {
+ version: 0,
+ count: 1,
+ pixels: 'http://testpixels.net',
+ ad: [DEFAULT_TEST_ARJ_AD_UNIT]
+ }
+ };
+
describe('inherited functions', () => {
it('exists and is a function', () => {
expect(adapter.callBids).to.exist.and.to.be.a('function');
@@ -53,12 +164,14 @@ describe('OpenxAdapter', () => {
bidder: 'openx',
params: {
unit: '12345678',
- delDomain: 'test-del-domain',
+ delDomain: 'test-del-domain'
},
adUnitCode: 'adunit-code',
- mediaTypes: {video: {
- playerSize: [640, 480]
- }},
+ mediaTypes: {
+ video: {
+ playerSize: [640, 480]
+ }
+ },
bidId: '30b31c1838de1e',
bidderRequestId: '22edbae2733bf6',
auctionId: '1d1a030790a475',
@@ -302,7 +415,7 @@ describe('OpenxAdapter', () => {
},
'params': {
'unit': '12345678',
- 'delDomain': 'test-del-domain',
+ 'delDomain': 'test-del-domain'
},
'adUnitCode': 'adunit-code',
'bidId': '30b31c1838de1e',
@@ -384,106 +497,275 @@ describe('OpenxAdapter', () => {
userSync.registerSync.restore();
});
- const bids = [{
- 'bidder': 'openx',
- 'params': {
- 'unit': '12345678',
- 'delDomain': 'test-del-domain'
- },
- 'adUnitCode': 'adunit-code',
- 'mediaType': 'banner',
- 'sizes': [[300, 250], [300, 600]],
- 'bidId': '30b31c1838de1e',
- 'bidderRequestId': '22edbae2733bf6',
- 'auctionId': '1d1a030790a475'
- }];
- const bidRequest = {
- method: 'GET',
- url: '//openx-d.openx.net/v/1.0/arj',
- data: {},
- payload: {'bids': bids, 'startTime': new Date()}
- };
-
- const bidResponse = {
- 'ads':
- {
- 'version': 1,
- 'count': 1,
- 'pixels': 'http://testpixels.net',
- 'ad': [
- {
- 'adunitid': 12345678,
- 'adid': 5678,
- 'type': 'html',
- 'html': 'test_html',
- 'framed': 1,
- 'is_fallback': 0,
- 'ts': 'ts',
- 'cpipc': 1000,
- 'pub_rev': '1000',
- 'adv_id': 'adv_id',
- 'brand_id': '',
- 'creative': [
- {
- 'width': '300',
- 'height': '250',
- 'target': '_blank',
- 'mime': 'text/html',
- 'media': 'test_media',
- 'tracking': {
- 'impression': 'http://openx-d.openx.net/v/1.0/ri?ts=ts',
- 'inview': 'test_inview',
- 'click': 'test_click'
- }
- }
- ]
- }]
+ describe('when there is a standard response', function () {
+ const creativeOverride = {
+ id: 234,
+ width: '300',
+ height: '250',
+ tracking: {
+ impression: 'http://openx-d.openx.net/v/1.0/ri?ts=ts'
}
+ };
- };
- it('should return correct bid response', () => {
- const expectedResponse = [
- {
- 'requestId': '30b31c1838de1e',
- 'cpm': 1,
- 'width': '300',
- 'height': '250',
- 'creativeId': 5678,
- 'ad': 'test_html',
- 'ttl': 300,
- 'netRevenue': true,
- 'currency': 'USD',
- 'ts': 'ts'
- }
- ];
+ const adUnitOverride = {
+ ts: 'test-1234567890-ts',
+ idx: '0',
+ currency: 'USD',
+ pub_rev: '10000',
+ html: 'OpenX Ad
'
+ };
+ let adUnit;
+ let bidResponse;
+
+ let bid;
+ let bidRequest;
+ let bidRequestConfigs;
- const result = spec.interpretResponse({body: bidResponse}, bidRequest);
- expect(Object.keys(result[0])).to.eql(Object.keys(expectedResponse[0]));
+ beforeEach(function () {
+ bidRequestConfigs = [{
+ 'bidder': 'openx',
+ 'params': {
+ 'unit': '12345678',
+ 'delDomain': 'test-del-domain'
+ },
+ 'adUnitCode': 'adunit-code',
+ 'mediaType': 'banner',
+ 'sizes': [[300, 250], [300, 600]],
+ 'bidId': '30b31c1838de1e',
+ 'bidderRequestId': '22edbae2733bf6',
+ 'auctionId': '1d1a030790a475'
+ }];
+
+ bidRequest = {
+ method: 'GET',
+ url: '//openx-d.openx.net/v/1.0/arj',
+ data: {},
+ payload: {'bids': bidRequestConfigs, 'startTime': new Date()}
+ };
+
+ adUnit = mockAdUnit(adUnitOverride, creativeOverride);
+ bidResponse = mockArjResponse(undefined, [adUnit]);
+ bid = spec.interpretResponse({body: bidResponse}, bidRequest)[0];
+ });
+
+ it('should return a price', function () {
+ expect(bid.cpm).to.equal(parseInt(adUnitOverride.pub_rev, 10) / 1000);
+ });
+
+ it('should return a request id', function () {
+ expect(bid.requestId).to.equal(bidRequest.payload.bids[0].bidId);
+ });
+
+ it('should return width and height for the creative', function () {
+ expect(bid.width).to.equal(creativeOverride.width);
+ expect(bid.height).to.equal(creativeOverride.height);
+ });
+
+ it('should return a creativeId', function () {
+ expect(bid.creativeId).to.equal(creativeOverride.id);
+ });
+
+ it('should return an ad', function () {
+ expect(bid.ad).to.equal(adUnitOverride.html);
+ });
+
+ it('should have a time-to-live of 5 minutes', function () {
+ expect(bid.ttl).to.equal(300);
+ });
+
+ it('should always return net revenue', function () {
+ expect(bid.netRevenue).to.equal(true);
+ });
+
+ it('should return a currency', function () {
+ expect(bid.currency).to.equal(adUnitOverride.currency);
+ });
+
+ it('should return a transaction state', function () {
+ expect(bid.ts).to.equal(adUnitOverride.ts);
+ });
+
+ it('should register a beacon', () => {
+ spec.interpretResponse({body: bidResponse}, bidRequest);
+ sinon.assert.calledWith(userSync.registerSync, 'image', 'openx', sinon.match(new RegExp(`\/\/openx-d\.openx\.net.*\/bo\?.*ts=${adUnitOverride.ts}`)));
+ });
});
- it('handles nobid responses', () => {
- const bidResponse = {
- 'ads':
- {
- 'version': 1,
- 'count': 1,
- 'pixels': 'http://testpixels.net',
- 'ad': []
- }
+ describe('when there is a deal', function () {
+ const adUnitOverride = {
+ deal_id: 'ox-1000'
};
+ let adUnit;
+ let bidResponse;
- const result = spec.interpretResponse({body: bidResponse}, bidRequest);
- expect(result.length).to.equal(0);
+ let bid;
+ let bidRequestConfigs;
+ let bidRequest;
+
+ beforeEach(function () {
+ bidRequestConfigs = [{
+ 'bidder': 'openx',
+ 'params': {
+ 'unit': '12345678',
+ 'delDomain': 'test-del-domain'
+ },
+ 'adUnitCode': 'adunit-code',
+ 'mediaType': 'banner',
+ 'sizes': [[300, 250], [300, 600]],
+ 'bidId': '30b31c1838de1e',
+ 'bidderRequestId': '22edbae2733bf6',
+ 'auctionId': '1d1a030790a475'
+ }];
+
+ bidRequest = {
+ method: 'GET',
+ url: '//openx-d.openx.net/v/1.0/arj',
+ data: {},
+ payload: {'bids': bidRequestConfigs, 'startTime': new Date()}
+ };
+ adUnit = mockAdUnit(adUnitOverride);
+ bidResponse = mockArjResponse(null, [adUnit]);
+ bid = spec.interpretResponse({body: bidResponse}, bidRequest)[0];
+ mockArjResponse();
+ });
+
+ it('should return a deal id', function () {
+ expect(bid.dealId).to.equal(adUnitOverride.deal_id);
+ });
});
- it('should register a user sync', () => {
- spec.interpretResponse({body: bidResponse}, bidRequest);
- sinon.assert.calledWith(userSync.registerSync, 'iframe', 'openx', 'http://testpixels.net');
+ describe('when there is no bids in the response', function () {
+ let bidRequest;
+ let bidRequestConfigs;
+
+ beforeEach(function () {
+ bidRequestConfigs = [{
+ 'bidder': 'openx',
+ 'params': {
+ 'unit': '12345678',
+ 'delDomain': 'test-del-domain'
+ },
+ 'adUnitCode': 'adunit-code',
+ 'mediaType': 'banner',
+ 'sizes': [[300, 250], [300, 600]],
+ 'bidId': '30b31c1838de1e',
+ 'bidderRequestId': '22edbae2733bf6',
+ 'auctionId': '1d1a030790a475'
+ }];
+
+ bidRequest = {
+ method: 'GET',
+ url: '//openx-d.openx.net/v/1.0/arj',
+ data: {},
+ payload: {'bids': bidRequestConfigs, 'startTime': new Date()}
+ };
+ });
+
+ it('handles nobid responses', () => {
+ const bidResponse = {
+ 'ads':
+ {
+ 'version': 1,
+ 'count': 1,
+ 'pixels': 'http://testpixels.net',
+ 'ad': []
+ }
+ };
+
+ const result = spec.interpretResponse({body: bidResponse}, bidRequest);
+ expect(result.length).to.equal(0);
+ });
});
- it('should register a beacon', () => {
- spec.interpretResponse({body: bidResponse}, bidRequest);
- sinon.assert.calledWith(userSync.registerSync, 'image', 'openx', sinon.match(/\/\/openx-d\.openx\.net.*\/bo\?.*ts=ts/));
+ describe('when adunits return out of order', function () {
+ const bidRequests = [{
+ bidder: 'openx',
+ params: {
+ unit: '12345678',
+ delDomain: 'test-del-domain'
+ },
+ adUnitCode: 'adunit-code',
+ mediaTypes: {
+ banner: {
+ sizes: [[100, 111]]
+ }
+ },
+ bidId: 'test-bid-request-id-1',
+ bidderRequestId: 'test-request-1',
+ auctionId: 'test-auction-id-1'
+ }, {
+ bidder: 'openx',
+ params: {
+ unit: '12345678',
+ delDomain: 'test-del-domain'
+ },
+ adUnitCode: 'adunit-code',
+ mediaTypes: {
+ banner: {
+ sizes: [[200, 222]]
+ }
+ },
+ bidId: 'test-bid-request-id-2',
+ bidderRequestId: 'test-request-1',
+ auctionId: 'test-auction-id-1'
+ }, {
+ bidder: 'openx',
+ params: {
+ unit: '12345678',
+ delDomain: 'test-del-domain'
+ },
+ adUnitCode: 'adunit-code',
+ mediaTypes: {
+ banner: {
+ sizes: [[300, 333]]
+ }
+ },
+ 'bidId': 'test-bid-request-id-3',
+ 'bidderRequestId': 'test-request-1',
+ 'auctionId': 'test-auction-id-1'
+ }];
+ const bidRequest = {
+ method: 'GET',
+ url: '//openx-d.openx.net/v/1.0/arj',
+ data: {},
+ payload: {'bids': bidRequests, 'startTime': new Date()}
+ };
+
+ let outOfOrderAdunits = [
+ mockAdUnit({
+ idx: '1'
+ }, {
+ width: bidRequests[1].mediaTypes.banner.sizes[0][0],
+ height: bidRequests[1].mediaTypes.banner.sizes[0][1]
+ }),
+ mockAdUnit({
+ idx: '2'
+ }, {
+ width: bidRequests[2].mediaTypes.banner.sizes[0][0],
+ height: bidRequests[2].mediaTypes.banner.sizes[0][1]
+ }),
+ mockAdUnit({
+ idx: '0'
+ }, {
+ width: bidRequests[0].mediaTypes.banner.sizes[0][0],
+ height: bidRequests[0].mediaTypes.banner.sizes[0][1]
+ })
+ ];
+
+ let bidResponse = mockArjResponse(undefined, outOfOrderAdunits);
+
+ it('should return map adunits back to the proper request', function () {
+ const bids = spec.interpretResponse({body: bidResponse}, bidRequest);
+ expect(bids[0].requestId).to.equal(bidRequests[1].bidId);
+ expect(bids[0].width).to.equal(bidRequests[1].mediaTypes.banner.sizes[0][0]);
+ expect(bids[0].height).to.equal(bidRequests[1].mediaTypes.banner.sizes[0][1]);
+ expect(bids[1].requestId).to.equal(bidRequests[2].bidId);
+ expect(bids[1].width).to.equal(bidRequests[2].mediaTypes.banner.sizes[0][0]);
+ expect(bids[1].height).to.equal(bidRequests[2].mediaTypes.banner.sizes[0][1]);
+ expect(bids[2].requestId).to.equal(bidRequests[0].bidId);
+ expect(bids[2].width).to.equal(bidRequests[0].mediaTypes.banner.sizes[0][0]);
+ expect(bids[2].height).to.equal(bidRequests[0].mediaTypes.banner.sizes[0][1]);
+ });
});
});
@@ -599,11 +881,6 @@ describe('OpenxAdapter', () => {
expect(result.length).to.equal(0);
});
- it('should register a user sync', () => {
- spec.interpretResponse({body: bidResponse}, bidRequestsWithMediaTypes);
- sinon.assert.calledWith(userSync.registerSync, 'iframe', 'openx', 'http://testpixels.net');
- });
-
it('should register a beacon', () => {
spec.interpretResponse({body: bidResponse}, bidRequestsWithMediaTypes);
sinon.assert.calledWith(userSync.registerSync, 'image', 'openx', sinon.match(/^\/\/test-colo\.com/))
@@ -611,4 +888,102 @@ describe('OpenxAdapter', () => {
sinon.assert.calledWith(userSync.registerSync, 'image', 'openx', sinon.match(/ts=test-ts/));
});
});
+
+ describe('user sync', () => {
+ const syncUrl = 'http://testpixels.net';
+
+ it('should register the pixel iframe from banner ad response', () => {
+ let syncs = spec.getUserSyncs(
+ { iframeEnabled: true },
+ [{ body: { ads: { pixels: syncUrl } } }]
+ );
+ expect(syncs).to.deep.equal([{ type: 'iframe', url: syncUrl }]);
+ });
+
+ it('should register the pixel iframe from video ad response', () => {
+ let syncs = spec.getUserSyncs(
+ {iframeEnabled: true},
+ [{body: {pixels: syncUrl}}]
+ );
+ expect(syncs).to.deep.equal([{type: 'iframe', url: syncUrl}]);
+ });
+
+ it('should register the default iframe if no pixels available', () => {
+ let syncs = spec.getUserSyncs(
+ {iframeEnabled: true},
+ []
+ );
+ expect(syncs).to.deep.equal([{type: 'iframe', url: '//u.openx.net/w/1.0/pd'}]);
+ });
+ });
+
+ /**
+ * Makes sure the override object does not introduce
+ * new fields against the contract
+ *
+ * This does a shallow check in order to make key checking simple
+ * with respect to what a helper handles. For helpers that have
+ * nested fields, either check your design on maybe breaking it up
+ * to smaller, manageable pieces
+ *
+ * OR just call this on your nth level field if necessary.
+ *
+ * @param {Object} override Object with keys that overrides the default
+ * @param {Object} contract Original object contains the default fields
+ * @param {string} typeName Name of the type we're checking for error messages
+ * @throws {AssertionError}
+ */
+ function overrideKeyCheck(override, contract, typeName) {
+ expect(contract).to.include.all.keys(Object.keys(override));
+ }
+
+ /**
+ * Creates a mock ArjResponse
+ * @param {OxArjResponse=} response
+ * @param {Array=} adUnits
+ * @throws {AssertionError}
+ * @return {OxArjResponse}
+ */
+ function mockArjResponse(response, adUnits = []) {
+ let mockedArjResponse = utils.deepClone(DEFAULT_ARJ_RESPONSE);
+
+ if (response) {
+ overrideKeyCheck(response, DEFAULT_ARJ_RESPONSE, 'OxArjResponse');
+ overrideKeyCheck(response.ads, DEFAULT_ARJ_RESPONSE.ads, 'OxArjResponse');
+ Object.assign(mockedArjResponse, response);
+ }
+
+ if (adUnits.length) {
+ mockedArjResponse.ads.count = adUnits.length;
+ mockedArjResponse.ads.ad = adUnits.map((adUnit, index) => {
+ overrideKeyCheck(adUnit, DEFAULT_TEST_ARJ_AD_UNIT, 'OxArjAdUnit');
+ return Object.assign(utils.deepClone(DEFAULT_TEST_ARJ_AD_UNIT), adUnit);
+ });
+ }
+
+ return mockedArjResponse;
+ }
+
+ /**
+ * Creates a mock ArjAdUnit
+ * @param {OxArjAdUnit=} adUnit
+ * @param {OxArjCreative=} creative
+ * @throws {AssertionError}
+ * @return {OxArjAdUnit}
+ */
+ function mockAdUnit(adUnit, creative) {
+ overrideKeyCheck(adUnit, DEFAULT_TEST_ARJ_AD_UNIT, 'OxArjAdUnit');
+
+ let mockedAdUnit = Object.assign(utils.deepClone(DEFAULT_TEST_ARJ_AD_UNIT), adUnit);
+
+ if (creative) {
+ overrideKeyCheck(creative, DEFAULT_TEST_ARJ_CREATIVE);
+ if (creative.tracking) {
+ overrideKeyCheck(creative.tracking, DEFAULT_TEST_ARJ_CREATIVE.tracking, 'OxArjCreative');
+ }
+ Object.assign(mockedAdUnit.creative[0], creative);
+ }
+
+ return mockedAdUnit;
+ }
});
diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js
index eef8a266b68f..cdb3113c2051 100644
--- a/test/spec/modules/prebidServerBidAdapter_spec.js
+++ b/test/spec/modules/prebidServerBidAdapter_spec.js
@@ -6,6 +6,7 @@ import cookie from 'src/cookie';
import { userSync } from 'src/userSync';
import { ajax } from 'src/ajax';
import { config } from 'src/config';
+import { requestBidsHook } from 'modules/consentManagement';
let CONFIG = {
accountId: '1',
@@ -391,6 +392,38 @@ describe('S2S Adapter', () => {
expect(requestBid.ad_units[0].bids[0].params.member).to.exist.and.to.be.a('string');
});
+ it('adds gdpr consent information to ortb2 request depending on module use', () => {
+ let ortb2Config = utils.deepClone(CONFIG);
+ ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'
+
+ let consentConfig = { consentManagement: { cmp: 'iab' }, s2sConfig: ortb2Config };
+ config.setConfig(consentConfig);
+
+ let gdprBidRequest = utils.deepClone(BID_REQUESTS);
+ gdprBidRequest[0].gdprConsent = {
+ consentString: 'abc123',
+ gdprApplies: true
+ };
+
+ adapter.callBids(REQUEST, gdprBidRequest, addBidResponse, done, ajax);
+ let requestBid = JSON.parse(requests[0].requestBody);
+
+ expect(requestBid.regs.ext.gdpr).is.equal(1);
+ expect(requestBid.user.ext.consent).is.equal('abc123');
+
+ config.resetConfig();
+ config.setConfig({s2sConfig: CONFIG});
+
+ adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax);
+ requestBid = JSON.parse(requests[1].requestBody);
+
+ expect(requestBid.regs).to.not.exist;
+ expect(requestBid.user).to.not.exist;
+
+ config.resetConfig();
+ $$PREBID_GLOBAL$$.requestBids.removeHook(requestBidsHook);
+ });
+
it('sets invalid cacheMarkup value to 0', () => {
const s2sConfig = Object.assign({}, CONFIG, {
cacheMarkup: 999
@@ -613,6 +646,7 @@ describe('S2S Adapter', () => {
expect(response).to.have.property('cache_id', '7654321');
expect(response).to.have.property('cache_url', 'http://www.test.com/cache?uuid=7654321');
expect(response).to.not.have.property('vastUrl');
+ expect(response).to.have.property('serverResponseTimeMs', 52);
});
it('registers video bids', () => {
@@ -721,6 +755,26 @@ describe('S2S Adapter', () => {
adapterManager.getBidAdapter.restore();
});
+ it('registers client user syncs when using OpenRTB endpoint', () => {
+ let rubiconAdapter = {
+ registerSyncs: sinon.spy()
+ };
+ sinon.stub(adapterManager, 'getBidAdapter').returns(rubiconAdapter);
+
+ const s2sConfig = Object.assign({}, CONFIG, {
+ endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'
+ });
+ config.setConfig({s2sConfig});
+
+ server.respondWith(JSON.stringify(RESPONSE_OPENRTB));
+ adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax);
+ server.respond();
+
+ sinon.assert.calledOnce(rubiconAdapter.registerSyncs);
+
+ adapterManager.getBidAdapter.restore();
+ });
+
it('registers bid responses when server requests cookie sync', () => {
server.respondWith(JSON.stringify(RESPONSE_NO_PBS_COOKIE));
@@ -805,6 +859,7 @@ describe('S2S Adapter', () => {
expect(response).to.have.property('bidderCode', 'appnexus');
expect(response).to.have.property('adId', '123');
expect(response).to.have.property('cpm', 0.5);
+ expect(response).to.have.property('serverResponseTimeMs', 8);
});
it('handles OpenRTB video responses', () => {
@@ -825,6 +880,7 @@ describe('S2S Adapter', () => {
expect(response).to.have.property('bidderCode', 'appnexus');
expect(response).to.have.property('adId', '123');
expect(response).to.have.property('cpm', 10);
+ expect(response).to.have.property('serverResponseTimeMs', 81);
});
it('should log warning for unsupported bidder', () => {
diff --git a/test/spec/modules/pubmaticBidAdapter_spec.js b/test/spec/modules/pubmaticBidAdapter_spec.js
index cbf17f9478aa..7ea10315a4e9 100644
--- a/test/spec/modules/pubmaticBidAdapter_spec.js
+++ b/test/spec/modules/pubmaticBidAdapter_spec.js
@@ -44,7 +44,10 @@ describe('PubMatic adapter', () => {
'price': 1.3,
'adm': 'image3.pubmatic.com Layer based creative',
'h': 250,
- 'w': 300
+ 'w': 300,
+ 'ext': {
+ 'deal_channel': 6
+ }
}]
}]
}
@@ -136,8 +139,44 @@ describe('PubMatic adapter', () => {
expect(data.ext.wrapper.wv).to.equal(constants.REPO_AND_VERSION); // Wrapper Version
expect(data.ext.wrapper.transactionId).to.equal(bidRequests[0].transactionId); // Prebid TransactionId
expect(data.ext.wrapper.wiid).to.equal(bidRequests[0].params.wiid); // OpenWrap: Wrapper Impression ID
- expect(data.ext.wrapper.profile).to.equal(bidRequests[0].params.profId); // OpenWrap: Wrapper Profile ID
- expect(data.ext.wrapper.version).to.equal(bidRequests[0].params.verId); // OpenWrap: Wrapper Profile Version ID
+ expect(data.ext.wrapper.profile).to.equal(parseInt(bidRequests[0].params.profId)); // OpenWrap: Wrapper Profile ID
+ expect(data.ext.wrapper.version).to.equal(parseInt(bidRequests[0].params.verId)); // OpenWrap: Wrapper Profile Version ID
+
+ expect(data.imp[0].id).to.equal(bidRequests[0].bidId); // Prebid bid id is passed as id
+ expect(data.imp[0].bidfloor).to.equal(parseFloat(bidRequests[0].params.kadfloor)); // kadfloor
+ expect(data.imp[0].tagid).to.equal('/15671365/DMDemo'); // tagid
+ expect(data.imp[0].banner.w).to.equal(300); // width
+ expect(data.imp[0].banner.h).to.equal(250); // height
+ expect(data.imp[0].ext.pmZoneId).to.equal(bidRequests[0].params.pmzoneid.split(',').slice(0, 50).map(id => id.trim()).join()); // pmzoneid
+ });
+
+ it('Request params check with GDPR Consent', () => {
+ let bidRequest = {
+ gdprConsent: {
+ consentString: 'kjfdniwjnifwenrif3',
+ gdprApplies: true
+ }
+ };
+ let request = spec.buildRequests(bidRequests, bidRequest);
+ let data = JSON.parse(request.data);
+ expect(data.user.ext.consent).to.equal('kjfdniwjnifwenrif3');
+ expect(data.regs.ext.gdpr).to.equal(1);
+ expect(data.at).to.equal(1); // auction type
+ expect(data.cur[0]).to.equal('USD'); // currency
+ expect(data.site.domain).to.be.a('string'); // domain should be set
+ expect(data.site.page).to.equal(bidRequests[0].params.kadpageurl); // forced pageURL
+ expect(data.site.publisher.id).to.equal(bidRequests[0].params.publisherId); // publisher Id
+ expect(data.user.yob).to.equal(parseInt(bidRequests[0].params.yob)); // YOB
+ expect(data.user.gender).to.equal(bidRequests[0].params.gender); // Gender
+ expect(data.device.geo.lat).to.equal(parseFloat(bidRequests[0].params.lat)); // Latitude
+ expect(data.device.geo.lon).to.equal(parseFloat(bidRequests[0].params.lon)); // Lognitude
+ expect(data.user.geo.lat).to.equal(parseFloat(bidRequests[0].params.lat)); // Latitude
+ expect(data.user.geo.lon).to.equal(parseFloat(bidRequests[0].params.lon)); // Lognitude
+ expect(data.ext.wrapper.wv).to.equal(constants.REPO_AND_VERSION); // Wrapper Version
+ expect(data.ext.wrapper.transactionId).to.equal(bidRequests[0].transactionId); // Prebid TransactionId
+ expect(data.ext.wrapper.wiid).to.equal(bidRequests[0].params.wiid); // OpenWrap: Wrapper Impression ID
+ expect(data.ext.wrapper.profile).to.equal(parseInt(bidRequests[0].params.profId)); // OpenWrap: Wrapper Profile ID
+ expect(data.ext.wrapper.version).to.equal(parseInt(bidRequests[0].params.verId)); // OpenWrap: Wrapper Profile Version ID
expect(data.imp[0].id).to.equal(bidRequests[0].bidId); // Prebid bid id is passed as id
expect(data.imp[0].bidfloor).to.equal(parseFloat(bidRequests[0].params.kadfloor)); // kadfloor
@@ -175,6 +214,24 @@ describe('PubMatic adapter', () => {
expect(response[0].referrer).to.include(utils.getTopWindowUrl());
expect(response[0].ad).to.equal(bidResponses.body.seatbid[0].bid[0].adm);
});
+
+ it('should check for dealChannel value selection', () => {
+ let request = spec.buildRequests(bidRequests);
+ let response = spec.interpretResponse(bidResponses, request);
+ expect(response).to.be.an('array').with.length.above(0);
+ expect(response[0].dealChannel).to.equal('PMPG');
+ });
+
+ it('should check for unexpected dealChannel value selection', () => {
+ let request = spec.buildRequests(bidRequests);
+ let updateBiResponse = bidResponses;
+ updateBiResponse.body.seatbid[0].bid[0].ext.deal_channel = 11;
+
+ let response = spec.interpretResponse(updateBiResponse, request);
+
+ expect(response).to.be.an('array').with.length.above(0);
+ expect(response[0].dealChannel).to.equal(null);
+ });
});
});
});
diff --git a/test/spec/modules/pulsepointBidAdapter_spec.js b/test/spec/modules/pulsepointBidAdapter_spec.js
index b4793256ee08..709dbeb76a26 100644
--- a/test/spec/modules/pulsepointBidAdapter_spec.js
+++ b/test/spec/modules/pulsepointBidAdapter_spec.js
@@ -273,4 +273,25 @@ describe('PulsePoint Adapter Tests', () => {
expect(ortbRequest.app.storeurl).to.equal('http://pulsepoint.com/apps');
expect(ortbRequest.app.domain).to.equal('pulsepoint.com');
});
+
+ it('Verify GDPR', () => {
+ const bidderRequest = {
+ gdprConsent: {
+ gdprApplies: true,
+ consentString: 'serialized_gpdr_data'
+ }
+ };
+ const request = spec.buildRequests(slotConfigs, bidderRequest);
+ expect(request.url).to.equal('//bid.contextweb.com/header/ortb');
+ expect(request.method).to.equal('POST');
+ const ortbRequest = JSON.parse(request.data);
+ // user object
+ expect(ortbRequest.user).to.not.equal(null);
+ expect(ortbRequest.user.ext).to.not.equal(null);
+ expect(ortbRequest.user.ext.consent).to.equal('serialized_gpdr_data');
+ // regs object
+ expect(ortbRequest.regs).to.not.equal(null);
+ expect(ortbRequest.regs.ext).to.not.equal(null);
+ expect(ortbRequest.regs.ext.gdpr).to.equal(1);
+ });
});
diff --git a/test/spec/modules/quantumBidAdapter_spec.js b/test/spec/modules/quantumBidAdapter_spec.js
index 2db1c0b0fbd9..053ec98ffaa1 100644
--- a/test/spec/modules/quantumBidAdapter_spec.js
+++ b/test/spec/modules/quantumBidAdapter_spec.js
@@ -5,7 +5,7 @@ import { newBidder } from 'src/adapters/bidderFactory'
const ENDPOINT = '//s.sspqns.com/hb'
const REQUEST = {
'bidder': 'quantum',
- 'sizes': [[300, 225]],
+ 'sizes': [[300, 250]],
'renderMode': 'banner',
'params': {
placementId: 21546
@@ -245,6 +245,7 @@ describe('quantumBidAdapter', () => {
expect(result[0]).to.have.property('cpm').equal(0.3)
expect(result[0]).to.have.property('width').to.be.below(2)
expect(result[0]).to.have.property('height').to.be.below(2)
+ expect(result[0]).to.have.property('mediaType').equal('native')
expect(result[0]).to.have.property('native')
})
@@ -252,8 +253,8 @@ describe('quantumBidAdapter', () => {
const result = spec.interpretResponse({body: serverResponse}, REQUEST)
expect(result[0]).to.have.property('cpm').equal(0.3)
expect(result[0]).to.have.property('width').equal(300)
- expect(result[0]).to.have.property('height').equal(225)
- // expect(result[0]).to.have.property('native');
+ expect(result[0]).to.have.property('height').equal(250)
+ expect(result[0]).to.have.property('mediaType').equal('banner')
expect(result[0]).to.have.property('ad')
})
diff --git a/test/spec/modules/readpeakBidAdapter_spec.js b/test/spec/modules/readpeakBidAdapter_spec.js
index 7da3450f16c3..776261c8db26 100644
--- a/test/spec/modules/readpeakBidAdapter_spec.js
+++ b/test/spec/modules/readpeakBidAdapter_spec.js
@@ -19,7 +19,8 @@ describe('ReadPeakAdapter', () => {
},
params: {
bidfloor: 5.00,
- publisherId: '11bc5dd5-7421-4dd8-c926-40fa653bec76'
+ publisherId: '11bc5dd5-7421-4dd8-c926-40fa653bec76',
+ siteId: '11bc5dd5-7421-4dd8-c926-40fa653bec77'
},
bidId: '2ffb201a808da7',
bidderRequestId: '178e34bad3658f',
@@ -63,8 +64,8 @@ describe('ReadPeakAdapter', () => {
img: {
type: 3,
url: 'http://url.to/image',
- w: 320,
- h: 200,
+ w: 750,
+ h: 500,
},
}],
link: {
@@ -97,7 +98,7 @@ describe('ReadPeakAdapter', () => {
'publisher': {
'id': '11bc5dd5-7421-4dd8-c926-40fa653bec76'
},
- 'id': '11bc5dd5-7421-4dd8-c926-40fa653bec76',
+ 'id': '11bc5dd5-7421-4dd8-c926-40fa653bec77',
'ref': '',
'page': 'http://localhost',
'domain': 'localhost'
@@ -151,15 +152,17 @@ describe('ReadPeakAdapter', () => {
const request = spec.buildRequests([ bidRequest ]);
const data = JSON.parse(request.data);
- expect(data.isPrebid).to.equal(true);
+
+ expect(data.source.ext.prebid).to.equal('$prebid.version$');
expect(data.id).to.equal(bidRequest.bidderRequestId)
expect(data.imp[0].bidfloor).to.equal(bidRequest.params.bidfloor);
expect(data.imp[0].bidfloorcur).to.equal('USD');
expect(data.site).to.deep.equal({
publisher: {
id: bidRequest.params.publisherId,
+ domain: 'http://localhost:9876',
},
- id: bidRequest.params.publisherId,
+ id: bidRequest.params.siteId,
ref: window.top.document.referrer,
page: utils.getTopWindowLocation().href,
domain: utils.getTopWindowLocation().hostname,
@@ -188,7 +191,7 @@ describe('ReadPeakAdapter', () => {
expect(bidResponse.native.title).to.equal('Title')
expect(bidResponse.native.body).to.equal('Description')
- expect(bidResponse.native.image).to.equal('http://url.to/image')
+ expect(bidResponse.native.image).to.deep.equal({url: 'http://url.to/image', width: 750, height: 500})
expect(bidResponse.native.clickUrl).to.equal('http%3A%2F%2Furl.to%2Ftarget')
expect(bidResponse.native.impressionTrackers).to.contain('http://url.to/pixeltracker')
});
diff --git a/test/spec/modules/rubiconAnalyticsAdapter_spec.js b/test/spec/modules/rubiconAnalyticsAdapter_spec.js
index d8f0811e81c9..38f8b726cd1f 100644
--- a/test/spec/modules/rubiconAnalyticsAdapter_spec.js
+++ b/test/spec/modules/rubiconAnalyticsAdapter_spec.js
@@ -84,6 +84,8 @@ const BID2 = Object.assign({}, BID, {
height: 90,
mediaType: 'banner',
cpm: 1.52,
+ source: 'server',
+ serverResponseTimeMs: 42,
rubiconTargeting: {
'rpfl_elemid': '/19968336/header-bid-tag1',
'rpfl_14062': '2_tier0100'
@@ -93,7 +95,7 @@ const BID2 = Object.assign({}, BID, {
'hb_adid': '3bd4ebb1c900e2',
'hb_pb': '1.500',
'hb_size': '728x90',
- 'hb_source': 'client'
+ 'hb_source': 'server'
}
});
@@ -103,7 +105,7 @@ const MOCK = {
[BID2.adUnitCode]: BID2.adserverTargeting
},
AUCTION_INIT: {
- 'timestamp': 1519149536560,
+ 'timestamp': 1519767010567,
'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa',
'timeout': 3000
},
@@ -237,7 +239,7 @@ const ANALYTICS_MESSAGE = {
'bidId': '2ecff0db240757',
'status': 'success',
'source': 'client',
- 'clientLatencyMillis': 617477221,
+ 'clientLatencyMillis': 3214,
'params': {
'accountId': '14062',
'siteId': '70608',
@@ -280,15 +282,16 @@ const ANALYTICS_MESSAGE = {
'hb_adid': '3bd4ebb1c900e2',
'hb_pb': '1.500',
'hb_size': '728x90',
- 'hb_source': 'client'
+ 'hb_source': 'server'
},
'bids': [
{
'bidder': 'rubicon',
'bidId': '3bd4ebb1c900e2',
'status': 'success',
- 'source': 'client',
- 'clientLatencyMillis': 617477221,
+ 'source': 'server',
+ 'clientLatencyMillis': 3214,
+ 'serverLatencyMillis': 42,
'params': {
'accountId': '14062',
'siteId': '70608',
@@ -316,7 +319,7 @@ const ANALYTICS_MESSAGE = {
'bidId': '2ecff0db240757',
'status': 'success',
'source': 'client',
- 'clientLatencyMillis': 617477221,
+ 'clientLatencyMillis': 3214,
'samplingFactor': 1,
'accountId': 1001,
'params': {
@@ -351,8 +354,9 @@ const ANALYTICS_MESSAGE = {
'adUnitCode': '/19968336/header-bid-tag1',
'bidId': '3bd4ebb1c900e2',
'status': 'success',
- 'source': 'client',
- 'clientLatencyMillis': 617477221,
+ 'source': 'server',
+ 'clientLatencyMillis': 3214,
+ 'serverLatencyMillis': 42,
'samplingFactor': 1,
'accountId': 1001,
'params': {
@@ -368,7 +372,7 @@ const ANALYTICS_MESSAGE = {
'hb_adid': '3bd4ebb1c900e2',
'hb_pb': '1.500',
'hb_size': '728x90',
- 'hb_source': 'client'
+ 'hb_source': 'server'
},
'bidResponse': {
'bidPriceUSD': 1.52,
diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js
index 954348935df9..e3ffef9997ea 100644
--- a/test/spec/modules/rubiconBidAdapter_spec.js
+++ b/test/spec/modules/rubiconBidAdapter_spec.js
@@ -1,10 +1,10 @@
-import { expect } from 'chai';
+import {expect} from 'chai';
import adapterManager from 'src/adaptermanager';
-import { spec, masSizeOrdering, resetUserSync } from 'modules/rubiconBidAdapter';
-import { parse as parseQuery } from 'querystring';
-import { newBidder } from 'src/adapters/bidderFactory';
-import { userSync } from 'src/userSync';
-import { config } from 'src/config';
+import {spec, masSizeOrdering, resetUserSync} from 'modules/rubiconBidAdapter';
+import {parse as parseQuery} from 'querystring';
+import {newBidder} from 'src/adapters/bidderFactory';
+import {userSync} from 'src/userSync';
+import {config} from 'src/config';
import * as utils from 'src/utils';
var CONSTANTS = require('src/constants.json');
@@ -15,15 +15,31 @@ describe('the rubicon adapter', () => {
let sandbox,
bidderRequest;
+ /**
+ * @param {boolean} [gdprApplies]
+ */
+ function createGdprBidderRequest(gdprApplies) {
+ if (typeof gdprApplies === 'boolean') {
+ bidderRequest.gdprConsent = {
+ 'consentString': 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==',
+ 'gdprApplies': gdprApplies
+ };
+ } else {
+ bidderRequest.gdprConsent = {
+ 'consentString': 'BOJ/P2HOJ/P2HABABMAAAAAZ+A=='
+ };
+ }
+ }
+
function createVideoBidderRequest() {
- let bid = bidderRequest.bids[0];
+ createGdprBidderRequest(true);
+ let bid = bidderRequest.bids[0];
bid.mediaTypes = {
video: {
context: 'instream'
}
};
-
bid.params.video = {
'language': 'en',
'p_aso.video.ext.skip': true,
@@ -39,8 +55,9 @@ describe('the rubicon adapter', () => {
}
function createLegacyVideoBidderRequest() {
- let bid = bidderRequest.bids[0];
+ createGdprBidderRequest(true);
+ let bid = bidderRequest.bids[0];
// Legacy property (Prebid <1.0)
bid.mediaType = 'video';
bid.params.video = {
@@ -246,7 +263,7 @@ describe('the rubicon adapter', () => {
expect(parseQuery(request.data).rf).to.equal('http://www.prebid.org');
let origGetConfig = config.getConfig;
- sandbox.stub(config, 'getConfig').callsFake(function(key) {
+ sandbox.stub(config, 'getConfig').callsFake(function (key) {
if (key === 'pageUrl') {
return 'http://www.rubiconproject.com';
}
@@ -301,7 +318,8 @@ describe('the rubicon adapter', () => {
it('should send digitrust params', () => {
window.DigiTrust = {
- getUser: function() {}
+ getUser: function () {
+ }
};
sandbox.stub(window.DigiTrust, 'getUser').callsFake(() =>
({
@@ -346,7 +364,8 @@ describe('the rubicon adapter', () => {
it('should not send digitrust params due to optout', () => {
window.DigiTrust = {
- getUser: function() {}
+ getUser: function () {
+ }
};
sandbox.stub(window.DigiTrust, 'getUser').callsFake(() =>
({
@@ -374,7 +393,8 @@ describe('the rubicon adapter', () => {
it('should not send digitrust params due to failure', () => {
window.DigiTrust = {
- getUser: function() {}
+ getUser: function () {
+ }
};
sandbox.stub(window.DigiTrust, 'getUser').callsFake(() =>
({
@@ -523,6 +543,46 @@ describe('the rubicon adapter', () => {
expect(window.DigiTrust.getUser.calledOnce).to.equal(true);
});
});
+
+ describe('GDPR consent config', () => {
+ it('should send "gdpr" and "gdpr_consent", when gdprConsent defines consentString and gdprApplies', () => {
+ createGdprBidderRequest(true);
+ let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest);
+ let data = parseQuery(request.data);
+
+ expect(data['gdpr']).to.equal('1');
+ expect(data['gdpr_consent']).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A==');
+ });
+
+ it('should send only "gdpr_consent", when gdprConsent defines only consentString', () => {
+ createGdprBidderRequest();
+ let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest);
+ let data = parseQuery(request.data);
+
+ expect(data['gdpr_consent']).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A==');
+ expect(data['gdpr']).to.equal(undefined);
+ });
+
+ it('should not send GDPR params if gdprConsent is not defined', () => {
+ let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest);
+ let data = parseQuery(request.data);
+
+ expect(data['gdpr']).to.equal(undefined);
+ expect(data['gdpr_consent']).to.equal(undefined);
+ });
+
+ it('should set "gdpr" value as 1 or 0, using "gdprApplies" value of either true/false', () => {
+ createGdprBidderRequest(true);
+ let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest);
+ let data = parseQuery(request.data);
+ expect(data['gdpr']).to.equal('1');
+
+ createGdprBidderRequest(false);
+ [request] = spec.buildRequests(bidderRequest.bids, bidderRequest);
+ data = parseQuery(request.data);
+ expect(data['gdpr']).to.equal('0');
+ });
+ });
});
describe('for video requests', () => {
@@ -548,6 +608,8 @@ describe('the rubicon adapter', () => {
expect(post).to.have.property('timeout').that.is.a('number');
expect(post.timeout < 5000).to.equal(true);
expect(post.stash_creatives).to.equal(true);
+ expect(post.gdpr_consent).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A==');
+ expect(post.gdpr).to.equal(1);
expect(post).to.have.property('ae_pass_through_parameters');
expect(post.ae_pass_through_parameters)
@@ -609,6 +671,8 @@ describe('the rubicon adapter', () => {
expect(post).to.have.property('timeout').that.is.a('number');
expect(post.timeout < 5000).to.equal(true);
expect(post.stash_creatives).to.equal(true);
+ expect(post.gdpr_consent).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A==');
+ expect(post.gdpr).to.equal(1);
expect(post).to.have.property('ae_pass_through_parameters');
expect(post.ae_pass_through_parameters)
@@ -752,7 +816,7 @@ describe('the rubicon adapter', () => {
bidRequestCopy.params.video = 123;
expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(false);
- bidRequestCopy.params.video = { size_id: undefined };
+ bidRequestCopy.params.video = {size_id: undefined};
expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(false);
delete bidRequestCopy.params.video;
@@ -936,7 +1000,7 @@ describe('the rubicon adapter', () => {
]
};
- let bids = spec.interpretResponse({ body: response }, {
+ let bids = spec.interpretResponse({body: response}, {
bidRequest: bidderRequest.bids[0]
});
@@ -992,7 +1056,7 @@ describe('the rubicon adapter', () => {
}]
};
- let bids = spec.interpretResponse({ body: response }, {
+ let bids = spec.interpretResponse({body: response}, {
bidRequest: bidderRequest.bids[0]
});
@@ -1015,7 +1079,7 @@ describe('the rubicon adapter', () => {
'ads': []
};
- let bids = spec.interpretResponse({ body: response }, {
+ let bids = spec.interpretResponse({body: response}, {
bidRequest: bidderRequest.bids[0]
});
@@ -1039,7 +1103,7 @@ describe('the rubicon adapter', () => {
}]
};
- let bids = spec.interpretResponse({ body: response }, {
+ let bids = spec.interpretResponse({body: response}, {
bidRequest: bidderRequest.bids[0]
});
@@ -1049,7 +1113,7 @@ describe('the rubicon adapter', () => {
it('should handle an error because of malformed json response', () => {
let response = '{test{';
- let bids = spec.interpretResponse({ body: response }, {
+ let bids = spec.interpretResponse({body: response}, {
bidRequest: bidderRequest.bids[0]
});
@@ -1090,7 +1154,7 @@ describe('the rubicon adapter', () => {
'account_id': 7780
};
- let bids = spec.interpretResponse({ body: response }, {
+ let bids = spec.interpretResponse({body: response}, {
bidRequest: bidderRequest.bids[0]
});
@@ -1112,7 +1176,7 @@ describe('the rubicon adapter', () => {
});
describe('user sync', () => {
- const emilyUrl = 'https://tap-secure.rubiconproject.com/partner/scripts/rubicon/emily.html?rtb_ext=1';
+ const emilyUrl = 'https://eus.rubiconproject.com/usync.html';
beforeEach(() => {
resetUserSync();
diff --git a/test/spec/modules/sonobiBidAdapter_spec.js b/test/spec/modules/sonobiBidAdapter_spec.js
index 4fd5c13e65c5..791d07ba91af 100644
--- a/test/spec/modules/sonobiBidAdapter_spec.js
+++ b/test/spec/modules/sonobiBidAdapter_spec.js
@@ -1,5 +1,5 @@
import { expect } from 'chai'
-import { spec } from 'modules/sonobiBidAdapter'
+import { spec, _getPlatform } from 'modules/sonobiBidAdapter'
import { newBidder } from 'src/adapters/bidderFactory'
describe('SonobiBidAdapter', () => {
@@ -139,6 +139,7 @@ describe('SonobiBidAdapter', () => {
expect(bidRequests.data.pv).to.equal(bidRequestsPageViewID.data.pv)
expect(bidRequests.data.hfa).to.not.exist
expect(bidRequests.bidderRequests).to.eql(bidRequest);
+ expect(['mobile', 'tablet', 'desktop']).to.contain(bidRequests.data.vp);
})
it('should return a properly formatted request with hfa', () => {
@@ -151,6 +152,10 @@ describe('SonobiBidAdapter', () => {
expect(bidRequests.data.s).not.to.be.empty
expect(bidRequests.data.hfa).to.equal('hfakey')
})
+ it('should return null if there is nothing to bid on', () => {
+ const bidRequests = spec.buildRequests([{params: {}}])
+ expect(bidRequests).to.equal(null);
+ })
})
describe('.interpretResponse', () => {
@@ -287,4 +292,15 @@ describe('SonobiBidAdapter', () => {
expect(spec.getUserSyncs({ pixelEnabled: false }, bidResponse)).to.have.length(0);
})
})
+ describe('_getPlatform', () => {
+ it('should return mobile', () => {
+ expect(_getPlatform({innerWidth: 767})).to.equal('mobile')
+ })
+ it('should return tablet', () => {
+ expect(_getPlatform({innerWidth: 800})).to.equal('tablet')
+ })
+ it('should return desktop', () => {
+ expect(_getPlatform({innerWidth: 1000})).to.equal('desktop')
+ })
+ })
})
diff --git a/test/spec/modules/sovrnBidAdapter_spec.js b/test/spec/modules/sovrnBidAdapter_spec.js
index a440b3d43c4c..9ad257531861 100644
--- a/test/spec/modules/sovrnBidAdapter_spec.js
+++ b/test/spec/modules/sovrnBidAdapter_spec.js
@@ -106,22 +106,26 @@ describe('sovrnBidAdapter', function() {
});
describe('interpretResponse', () => {
- let response = {
- body: {
- 'id': '37386aade21a71',
- 'seatbid': [{
- 'bid': [{
- 'id': 'a_403370_332fdb9b064040ddbec05891bd13ab28',
- 'impid': '263c448586f5a1',
- 'price': 0.45882675,
- 'nurl': '',
- 'adm': '',
- 'h': 90,
- 'w': 728
+ let response;
+ beforeEach(() => {
+ response = {
+ body: {
+ 'id': '37386aade21a71',
+ 'seatbid': [{
+ 'bid': [{
+ 'id': 'a_403370_332fdb9b064040ddbec05891bd13ab28',
+ 'crid': 'creativelycreatedcreativecreative',
+ 'impid': '263c448586f5a1',
+ 'price': 0.45882675,
+ 'nurl': '',
+ 'adm': '',
+ 'h': 90,
+ 'w': 728
+ }]
}]
- }]
- }
- };
+ }
+ };
+ });
it('should get the correct bid response', () => {
let expectedResponse = [{
@@ -129,7 +133,27 @@ describe('sovrnBidAdapter', function() {
'cpm': 0.45882675,
'width': 728,
'height': 90,
- 'creativeId': 'a_403370_332fdb9b064040ddbec05891bd13ab28',
+ 'creativeId': 'creativelycreatedcreativecreative',
+ 'dealId': null,
+ 'currency': 'USD',
+ 'netRevenue': true,
+ 'mediaType': 'banner',
+ 'ad': decodeURIComponent(`
>`),
+ 'ttl': 60000
+ }];
+
+ let result = spec.interpretResponse(response);
+ expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse[0]));
+ });
+
+ it('crid should default to the bid id if not on the response', () => {
+ delete response.body.seatbid[0].bid[0].crid;
+ let expectedResponse = [{
+ 'requestId': '263c448586f5a1',
+ 'cpm': 0.45882675,
+ 'width': 728,
+ 'height': 90,
+ 'creativeId': response.body.seatbid[0].bid[0].id,
'dealId': null,
'currency': 'USD',
'netRevenue': true,
@@ -150,7 +174,7 @@ describe('sovrnBidAdapter', function() {
'cpm': 0.45882675,
'width': 728,
'height': 90,
- 'creativeId': 'a_403370_332fdb9b064040ddbec05891bd13ab28',
+ 'creativeId': 'creativelycreatedcreativecreative',
'dealId': 'baking',
'currency': 'USD',
'netRevenue': true,
diff --git a/test/spec/modules/yuktamediaAnalyticsAdaptor_spec.js b/test/spec/modules/yuktamediaAnalyticsAdaptor_spec.js
new file mode 100644
index 000000000000..29d23d66bd20
--- /dev/null
+++ b/test/spec/modules/yuktamediaAnalyticsAdaptor_spec.js
@@ -0,0 +1,177 @@
+import yuktamediaAnalyticsAdapter from 'modules/yuktamediaAnalyticsAdapter';
+import { expect } from 'chai';
+let adaptermanager = require('src/adaptermanager');
+let events = require('src/events');
+let constants = require('src/constants.json');
+
+describe('YuktaMedia analytics adapter', () => {
+ let xhr;
+ let requests;
+
+ beforeEach(() => {
+ xhr = sinon.useFakeXMLHttpRequest();
+ requests = [];
+ xhr.onCreate = request => requests.push(request);
+ sinon.stub(events, 'getEvents').returns([]);
+ });
+
+ afterEach(() => {
+ xhr.restore();
+ events.getEvents.restore();
+ });
+
+ describe('track', () => {
+ let initOptions = {
+ pubId: '1',
+ pubKey: 'ZXlKaGJHY2lPaUpJVXpJMU5pSjkuT=='
+ };
+
+ adaptermanager.registerAnalyticsAdapter({
+ code: 'yuktamedia',
+ adapter: yuktamediaAnalyticsAdapter
+ });
+
+ beforeEach(() => {
+ adaptermanager.enableAnalytics({
+ provider: 'yuktamedia',
+ options: initOptions
+ });
+ });
+
+ afterEach(() => {
+ yuktamediaAnalyticsAdapter.disableAnalytics();
+ });
+
+ it('builds and sends auction data', () => {
+ let auctionTimestamp = 1496510254313;
+ let bidRequest = {
+ 'bidderCode': 'appnexus',
+ 'auctionId': 'a5b849e5-87d7-4205-8300-d063084fcfb7',
+ 'bidderRequestId': '173209942f8bdd',
+ 'bids': [{
+ 'bidder': 'appnexus',
+ 'params': {
+ 'placementId': '10433394'
+ },
+ 'crumbs': {
+ 'pubcid': '9a2a4e71-f39b-405f-aecc-19efc22b618d'
+ },
+ 'adUnitCode': 'div-gpt-ad-1438287399331-0',
+ 'transactionId': '2f481ff1-8d20-4c28-8e36-e384e9e3eec6',
+ 'sizes': [
+ [300, 250],
+ [300, 600]
+ ],
+ 'bidId': '2eddfdc0c791dc',
+ 'bidderRequestId': '173209942f8bdd',
+ 'auctionId': 'a5b849e5-87d7-4205-8300-d063084fcfb7'
+ }
+ ],
+ 'auctionStart': 1522265863591,
+ 'timeout': 3000,
+ 'start': 1522265863600,
+ 'doneCbCallCount': 1
+ };
+ let bidResponse = {
+ 'height': 250,
+ 'statusMessage': 'Bid available',
+ 'adId': '2eddfdc0c791dc',
+ 'mediaType': 'banner',
+ 'source': 'client',
+ 'requestId': '2eddfdc0c791dc',
+ 'cpm': 0.5,
+ 'creativeId': 29681110,
+ 'currency': 'USD',
+ 'netRevenue': true,
+ 'ttl': 300,
+ 'auctionId': 'a5b849e5-87d7-4205-8300-d063084fcfb7',
+ 'responseTimestamp': 1522265866110,
+ 'requestTimestamp': 1522265863600,
+ 'bidder': 'appnexus',
+ 'adUnitCode': 'div-gpt-ad-1438287399331-0',
+ 'timeToRespond': 2510,
+ 'size': '300x250'
+ };
+ let bidTimeoutArgsV1 = [
+ {
+ bidId: '2baa51527bd015',
+ bidder: 'bidderOne',
+ adUnitCode: '/19968336/header-bid-tag-0',
+ auctionId: '66529d4c-8998-47c2-ab3e-5b953490b98f'
+ },
+ {
+ bidId: '6fe3b4c2c23092',
+ bidder: 'bidderTwo',
+ adUnitCode: '/19968336/header-bid-tag-0',
+ auctionId: '66529d4c-8998-47c2-ab3e-5b953490b98f'
+ }
+ ];
+ let bid = {
+ 'bidderCode': 'appnexus',
+ 'bidId': '2eddfdc0c791dc',
+ 'adUnitCode': 'div-gpt-ad-1438287399331-0',
+ 'requestId': '173209942f8bdd',
+ 'auctionId': 'a5b849e5-87d7-4205-8300-d063084fcfb7',
+ 'renderStatus': 2,
+ 'cpm': 0.5,
+ 'creativeId': 29681110,
+ 'currency': 'USD',
+ 'mediaType': 'banner',
+ 'netRevenue': true,
+ 'requestTimestamp': 1522265863600,
+ 'responseTimestamp': 1522265866110,
+ 'sizes': '300x250,300x600',
+ 'statusMessage': 'Bid available',
+ 'timeToRespond': 2510
+ }
+
+ // Step 1: Send auction init event
+ events.emit(constants.EVENTS.AUCTION_INIT, {
+ timestamp: auctionTimestamp
+ });
+
+ // Step 2: Send bid requested event
+ events.emit(constants.EVENTS.BID_REQUESTED, bidRequest);
+
+ // Step 3: Send bid response event
+ events.emit(constants.EVENTS.BID_RESPONSE, bidResponse);
+
+ // Step 4: Send bid time out event
+ events.emit(constants.EVENTS.BID_TIMEOUT, bidTimeoutArgsV1);
+
+ // Step 5: Send auction end event
+ events.emit(constants.EVENTS.AUCTION_END, {}, 'auctionEnd');
+
+ expect(requests.length).to.equal(1);
+
+ let auctionEventData = JSON.parse(requests[0].requestBody);
+
+ expect(auctionEventData.bids.length).to.equal(1);
+ expect(auctionEventData.bids[0]).to.deep.equal(bid);
+
+ expect(auctionEventData.initOptions).to.deep.equal(initOptions);
+
+ // Step 6: Send auction bid won event
+ events.emit(constants.EVENTS.BID_WON, {
+ 'bidderCode': 'appnexus',
+ 'statusMessage': 'Bid available',
+ 'adId': '108abedd106b669',
+ 'auctionId': '6355d610-7cdc-4009-a866-f91997fd24bb',
+ 'responseTimestamp': 1522144433058,
+ 'requestTimestamp': 1522144432923,
+ 'bidder': 'appnexus',
+ 'adUnitCode': 'div-gpt-ad-1438287399331-0',
+ 'timeToRespond': 135,
+ 'size': '300x250',
+ 'status': 'rendered'
+ }, 'won');
+
+ expect(requests.length).to.equal(2);
+
+ let winEventData = JSON.parse(requests[1].requestBody);
+
+ expect(winEventData.bidWon.status).to.equal('rendered');
+ expect(winEventData.initOptions).to.deep.equal(initOptions);
+ });
+ });
+});
diff --git a/test/spec/unit/core/adapterManager_spec.js b/test/spec/unit/core/adapterManager_spec.js
index 39e468d4959b..8b1c164a8043 100644
--- a/test/spec/unit/core/adapterManager_spec.js
+++ b/test/spec/unit/core/adapterManager_spec.js
@@ -716,141 +716,171 @@ describe('adapterManager tests', () => {
expect(AdapterManager.videoAdapters).to.include(alias);
});
});
+ });
+
+ describe('makeBidRequests', () => {
+ let adUnits;
+ beforeEach(() => {
+ adUnits = utils.deepClone(getAdUnits()).map(adUnit => {
+ adUnit.bids = adUnit.bids.filter(bid => includes(['appnexus', 'rubicon'], bid.bidder));
+ return adUnit;
+ })
+ });
- describe('makeBidRequests', () => {
- let adUnits;
+ describe('setBidderSequence', () => {
beforeEach(() => {
- adUnits = utils.deepClone(getAdUnits()).map(adUnit => {
- adUnit.bids = adUnit.bids.filter(bid => includes(['appnexus', 'rubicon'], bid.bidder));
- return adUnit;
- })
+ sinon.spy(utils, 'shuffle');
});
- describe('setBidderSequence', () => {
- beforeEach(() => {
- sinon.spy(utils, 'shuffle');
- });
-
- afterEach(() => {
- config.resetConfig();
- utils.shuffle.restore();
- });
+ afterEach(() => {
+ config.resetConfig();
+ utils.shuffle.restore();
+ });
- it('setting to `random` uses shuffled order of adUnits', () => {
- config.setConfig({ bidderSequence: 'random' });
- let bidRequests = AdapterManager.makeBidRequests(
- adUnits,
- Date.now(),
- utils.getUniqueIdentifierStr(),
- function callback() {},
- []
- );
- sinon.assert.calledOnce(utils.shuffle);
- });
+ it('setting to `random` uses shuffled order of adUnits', () => {
+ config.setConfig({ bidderSequence: 'random' });
+ let bidRequests = AdapterManager.makeBidRequests(
+ adUnits,
+ Date.now(),
+ utils.getUniqueIdentifierStr(),
+ function callback() {},
+ []
+ );
+ sinon.assert.calledOnce(utils.shuffle);
});
+ });
- describe('sizeMapping', () => {
- beforeEach(() => {
- sinon.stub(window, 'matchMedia').callsFake(() => ({matches: true}));
- });
+ describe('sizeMapping', () => {
+ beforeEach(() => {
+ sinon.stub(window, 'matchMedia').callsFake(() => ({matches: true}));
+ });
- afterEach(() => {
- matchMedia.restore();
- setSizeConfig([]);
- });
+ afterEach(() => {
+ matchMedia.restore();
+ setSizeConfig([]);
+ });
- it('should not filter bids w/ no labels', () => {
- let bidRequests = AdapterManager.makeBidRequests(
- adUnits,
- Date.now(),
- utils.getUniqueIdentifierStr(),
- function callback() {},
- []
- );
-
- expect(bidRequests.length).to.equal(2);
- let rubiconBidRequests = find(bidRequests, bidRequest => bidRequest.bidderCode === 'rubicon');
- expect(rubiconBidRequests.bids.length).to.equal(1);
- expect(rubiconBidRequests.bids[0].sizes).to.deep.equal(find(adUnits, adUnit => adUnit.code === rubiconBidRequests.bids[0].adUnitCode).sizes);
-
- let appnexusBidRequests = find(bidRequests, bidRequest => bidRequest.bidderCode === 'appnexus');
- expect(appnexusBidRequests.bids.length).to.equal(2);
- expect(appnexusBidRequests.bids[0].sizes).to.deep.equal(find(adUnits, adUnit => adUnit.code === appnexusBidRequests.bids[0].adUnitCode).sizes);
- expect(appnexusBidRequests.bids[1].sizes).to.deep.equal(find(adUnits, adUnit => adUnit.code === appnexusBidRequests.bids[1].adUnitCode).sizes);
- });
+ it('should not filter bids w/ no labels', () => {
+ let bidRequests = AdapterManager.makeBidRequests(
+ adUnits,
+ Date.now(),
+ utils.getUniqueIdentifierStr(),
+ function callback() {},
+ []
+ );
+
+ expect(bidRequests.length).to.equal(2);
+ let rubiconBidRequests = find(bidRequests, bidRequest => bidRequest.bidderCode === 'rubicon');
+ expect(rubiconBidRequests.bids.length).to.equal(1);
+ expect(rubiconBidRequests.bids[0].sizes).to.deep.equal(find(adUnits, adUnit => adUnit.code === rubiconBidRequests.bids[0].adUnitCode).sizes);
+
+ let appnexusBidRequests = find(bidRequests, bidRequest => bidRequest.bidderCode === 'appnexus');
+ expect(appnexusBidRequests.bids.length).to.equal(2);
+ expect(appnexusBidRequests.bids[0].sizes).to.deep.equal(find(adUnits, adUnit => adUnit.code === appnexusBidRequests.bids[0].adUnitCode).sizes);
+ expect(appnexusBidRequests.bids[1].sizes).to.deep.equal(find(adUnits, adUnit => adUnit.code === appnexusBidRequests.bids[1].adUnitCode).sizes);
+ });
- it('should filter sizes using size config', () => {
- let validSizes = [
- [728, 90],
- [300, 250]
- ];
-
- let validSizeMap = validSizes.map(size => size.toString()).reduce((map, size) => {
- map[size] = true;
- return map;
- }, {});
-
- setSizeConfig([{
- 'mediaQuery': '(min-width: 768px) and (max-width: 1199px)',
- 'sizesSupported': validSizes,
- 'labels': ['tablet', 'phone']
- }]);
-
- let bidRequests = AdapterManager.makeBidRequests(
- adUnits,
- Date.now(),
- utils.getUniqueIdentifierStr(),
- function callback() {},
- []
- );
+ it('should filter sizes using size config', () => {
+ let validSizes = [
+ [728, 90],
+ [300, 250]
+ ];
+
+ let validSizeMap = validSizes.map(size => size.toString()).reduce((map, size) => {
+ map[size] = true;
+ return map;
+ }, {});
+
+ setSizeConfig([{
+ 'mediaQuery': '(min-width: 768px) and (max-width: 1199px)',
+ 'sizesSupported': validSizes,
+ 'labels': ['tablet', 'phone']
+ }]);
+
+ let bidRequests = AdapterManager.makeBidRequests(
+ adUnits,
+ Date.now(),
+ utils.getUniqueIdentifierStr(),
+ function callback() {},
+ []
+ );
// only valid sizes as specified in size config should show up in bidRequests
- bidRequests.forEach(bidRequest => {
- bidRequest.bids.forEach(bid => {
- bid.sizes.forEach(size => {
- expect(validSizeMap[size]).to.equal(true);
- });
+ bidRequests.forEach(bidRequest => {
+ bidRequest.bids.forEach(bid => {
+ bid.sizes.forEach(size => {
+ expect(validSizeMap[size]).to.equal(true);
});
});
-
- setSizeConfig([{
- 'mediaQuery': '(min-width: 768px) and (max-width: 1199px)',
- 'sizesSupported': [],
- 'labels': ['tablet', 'phone']
- }]);
-
- bidRequests = AdapterManager.makeBidRequests(
- adUnits,
- Date.now(),
- utils.getUniqueIdentifierStr(),
- function callback() {},
- []
- );
-
- // if no valid sizes, all bidders should be filtered out
- expect(bidRequests.length).to.equal(0);
});
- it('should filter adUnits/bidders based on applied labels', () => {
- adUnits[0].labelAll = ['visitor-uk', 'mobile'];
- adUnits[1].labelAny = ['visitor-uk', 'desktop'];
- adUnits[1].bids[0].labelAny = ['mobile'];
- adUnits[1].bids[1].labelAll = ['desktop'];
+ setSizeConfig([{
+ 'mediaQuery': '(min-width: 768px) and (max-width: 1199px)',
+ 'sizesSupported': [],
+ 'labels': ['tablet', 'phone']
+ }]);
+
+ bidRequests = AdapterManager.makeBidRequests(
+ adUnits,
+ Date.now(),
+ utils.getUniqueIdentifierStr(),
+ function callback() {},
+ []
+ );
+
+ // if no valid sizes, all bidders should be filtered out
+ expect(bidRequests.length).to.equal(0);
+ });
+
+ it('should filter adUnits/bidders based on applied labels', () => {
+ adUnits[0].labelAll = ['visitor-uk', 'mobile'];
+ adUnits[1].labelAny = ['visitor-uk', 'desktop'];
+ adUnits[1].bids[0].labelAny = ['mobile'];
+ adUnits[1].bids[1].labelAll = ['desktop'];
- let bidRequests = AdapterManager.makeBidRequests(
- adUnits,
- Date.now(),
- utils.getUniqueIdentifierStr(),
- function callback() {},
- ['visitor-uk', 'desktop']
- );
+ let bidRequests = AdapterManager.makeBidRequests(
+ adUnits,
+ Date.now(),
+ utils.getUniqueIdentifierStr(),
+ function callback() {},
+ ['visitor-uk', 'desktop']
+ );
// only one adUnit and one bid from that adUnit should make it through the applied labels above
- expect(bidRequests.length).to.equal(1);
- expect(bidRequests[0].bidderCode).to.equal('rubicon');
- expect(bidRequests[0].bids.length).to.equal(1);
- expect(bidRequests[0].bids[0].adUnitCode).to.equal(adUnits[1].code);
+ expect(bidRequests.length).to.equal(1);
+ expect(bidRequests[0].bidderCode).to.equal('rubicon');
+ expect(bidRequests[0].bids.length).to.equal(1);
+ expect(bidRequests[0].bids[0].adUnitCode).to.equal(adUnits[1].code);
+ });
+ });
+
+ describe('gdpr consent module', () => {
+ it('inserts gdprConsent object to bidRequest only when module was enabled', () => {
+ AdapterManager.gdprDataHandler.setConsentData({
+ consentString: 'abc123def456',
+ consentRequired: true
});
+
+ let bidRequests = AdapterManager.makeBidRequests(
+ adUnits,
+ Date.now(),
+ utils.getUniqueIdentifierStr(),
+ function callback() {},
+ []
+ );
+ expect(bidRequests[0].gdprConsent.consentString).to.equal('abc123def456');
+ expect(bidRequests[0].gdprConsent.consentRequired).to.be.true;
+
+ AdapterManager.gdprDataHandler.setConsentData(null);
+
+ bidRequests = AdapterManager.makeBidRequests(
+ adUnits,
+ Date.now(),
+ utils.getUniqueIdentifierStr(),
+ function callback() {},
+ []
+ );
+ expect(bidRequests[0].gdprConsent).to.be.undefined;
});
});
});
diff --git a/test/spec/utils_spec.js b/test/spec/utils_spec.js
index f86840dbdba7..9218409c46c3 100755
--- a/test/spec/utils_spec.js
+++ b/test/spec/utils_spec.js
@@ -359,6 +359,33 @@ describe('Utils', function () {
});
});
+ describe('isPlainObject', function () {
+ it('should return false with input string', function () {
+ var output = utils.isPlainObject(obj_string);
+ assert.deepEqual(output, false);
+ });
+
+ it('should return false with input number', function () {
+ var output = utils.isPlainObject(obj_number);
+ assert.deepEqual(output, false);
+ });
+
+ it('should return true with input object', function () {
+ var output = utils.isPlainObject(obj_object);
+ assert.deepEqual(output, true);
+ });
+
+ it('should return false with input array', function () {
+ var output = utils.isPlainObject(obj_array);
+ assert.deepEqual(output, false);
+ });
+
+ it('should return false with input function', function () {
+ var output = utils.isPlainObject(obj_function);
+ assert.deepEqual(output, false);
+ });
+ });
+
describe('isEmpty', function () {
it('should return true with empty object', function () {
var output = utils.isEmpty(obj_object);