diff --git a/integrationExamples/gpt/adnuntius_multiformat_example.html b/integrationExamples/gpt/adnuntius_multiformat_example.html
new file mode 100644
index 00000000000..87b30d5887a
--- /dev/null
+++ b/integrationExamples/gpt/adnuntius_multiformat_example.html
@@ -0,0 +1,132 @@
+
+
+
+
+
+
+
+Adnuntius NATIVE
+Ad Slot 1
+
+
+
+
+
+
+
+
diff --git a/modules/adnuntiusBidAdapter.js b/modules/adnuntiusBidAdapter.js
index d017b6a8398..cce1b5332ad 100644
--- a/modules/adnuntiusBidAdapter.js
+++ b/modules/adnuntiusBidAdapter.js
@@ -1,5 +1,5 @@
import { registerBidder } from '../src/adapters/bidderFactory.js';
-import { BANNER, VIDEO } from '../src/mediaTypes.js';
+import {BANNER, VIDEO} from '../src/mediaTypes.js';
import {isStr, isEmpty, deepAccess, getUnixTimestampFromNow, convertObjectToArray} from '../src/utils.js';
import { config } from '../src/config.js';
import { getStorageManager } from '../src/storageManager.js';
@@ -12,13 +12,20 @@ const BIDDER_CODE_DEAL_ALIASES = [1, 2, 3, 4, 5].map(num => {
const ENDPOINT_URL = 'https://ads.adnuntius.delivery/i';
const ENDPOINT_URL_EUROPE = 'https://europe.delivery.adnuntius.com/i';
const GVLID = 855;
-const DEFAULT_VAST_VERSION = 'vast4'
+const SUPPORTED_MEDIA_TYPES = [BANNER, VIDEO];
const MAXIMUM_DEALS_LIMIT = 5;
const VALID_BID_TYPES = ['netBid', 'grossBid'];
const METADATA_KEY = 'adn.metaData';
const METADATA_KEY_SEPARATOR = '@@@';
export const misc = {
+ findHighestPrice: function(arr, bidType) {
+ return arr.reduce((highest, cur) => {
+ const currentBid = cur[bidType];
+ const highestBid = highest[bidType]
+ return currentBid.currency === highestBid.currency && currentBid.amount > highestBid.amount ? cur : highest;
+ }, arr[0]);
+ }
};
const storageTool = (function () {
@@ -219,7 +226,7 @@ export const spec = {
code: BIDDER_CODE,
aliases: BIDDER_CODE_DEAL_ALIASES,
gvlid: GVLID,
- supportedMediaTypes: [BANNER, VIDEO],
+ supportedMediaTypes: SUPPORTED_MEDIA_TYPES,
isBidRequestValid: function (bid) {
// The auId MUST be a hexadecimal string
const validAuId = AU_ID_REGEX.test(bid.params.auId);
@@ -266,10 +273,6 @@ export const spec = {
}
let network = bid.params.network || 'network';
- if (bid.mediaTypes && bid.mediaTypes.video && bid.mediaTypes.video.context !== 'outstream') {
- network += '_video'
- }
-
bidRequests[network] = bidRequests[network] || [];
bidRequests[network].push(bid);
@@ -291,20 +294,40 @@ export const spec = {
const bidTargeting = {...bid.params.targeting || {}};
targetingTool.mergeKvsFromOrtb(bidTargeting, bidderRequest);
- const adUnit = { ...bidTargeting, auId: bid.params.auId, targetId: bid.params.targetId || bid.bidId };
- const maxDeals = Math.max(0, Math.min(bid.params.maxDeals || 0, MAXIMUM_DEALS_LIMIT));
- if (maxDeals > 0) {
- adUnit.maxDeals = maxDeals;
+ const mediaTypes = bid.mediaTypes || {};
+ const validMediaTypes = SUPPORTED_MEDIA_TYPES.filter(mt => {
+ return mediaTypes[mt];
+ }) || [];
+ if (validMediaTypes.length === 0) {
+ // banner ads by default if nothing specified, dimensions to be derived from the ad unit within adnuntius system
+ validMediaTypes.push(BANNER);
}
- if (bid.mediaTypes && bid.mediaTypes.banner && bid.mediaTypes.banner.sizes) adUnit.dimensions = bid.mediaTypes.banner.sizes
- networks[network].adUnits.push(adUnit);
+ const isSingleFormat = validMediaTypes.length === 1;
+ validMediaTypes.forEach(mediaType => {
+ const mediaTypeData = mediaTypes[mediaType];
+ if (mediaType === VIDEO && mediaTypeData && mediaTypeData.context === 'outstream') {
+ return;
+ }
+ const targetId = (bid.params.targetId || bid.bidId) + (isSingleFormat || mediaType === BANNER ? '' : ('-' + mediaType));
+ const adUnit = {...bidTargeting, auId: bid.params.auId, targetId: targetId};
+ if (mediaType === VIDEO) {
+ adUnit.adType = 'VAST';
+ }
+ const maxDeals = Math.max(0, Math.min(bid.params.maxDeals || 0, MAXIMUM_DEALS_LIMIT));
+ if (maxDeals > 0) {
+ adUnit.maxDeals = maxDeals;
+ }
+ if (mediaType === BANNER && mediaTypeData && mediaTypeData.sizes) {
+ adUnit.dimensions = mediaTypeData.sizes;
+ }
+ networks[network].adUnits.push(adUnit);
+ });
}
const requests = [];
const networkKeys = Object.keys(networks);
for (let j = 0; j < networkKeys.length; j++) {
const network = networkKeys[j];
- if (network.indexOf('_video') > -1) { queryParamsAndValues.push('tt=' + DEFAULT_VAST_VERSION) }
const requestURL = gdprApplies ? ENDPOINT_URL_EUROPE : ENDPOINT_URL
requests.push({
method: 'POST',
@@ -321,7 +344,7 @@ export const spec = {
if (serverResponse.body.metaData) {
storageTool.saveToStorage(serverResponse.body.metaData, serverResponse.body.network);
}
- const adUnits = serverResponse.body.adUnits;
+ const responseAdUnits = serverResponse.body.adUnits;
let validatedBidType = validateBidType(config.getConfig().bidType);
if (bidRequest.bid) {
@@ -367,6 +390,35 @@ export const spec = {
return adResponse;
}
+ const highestYieldingAdUnits = [];
+ if (responseAdUnits.length === 1) {
+ highestYieldingAdUnits.push(responseAdUnits[0]);
+ } else if (responseAdUnits.length > 1) {
+ bidRequest.bid.forEach((resp) => {
+ const multiFormatAdUnits = [];
+ SUPPORTED_MEDIA_TYPES.forEach((mediaType) => {
+ const suffix = mediaType === BANNER ? '' : '-' + mediaType;
+ const targetId = (resp?.params?.targetId || resp.bidId) + suffix;
+
+ const au = responseAdUnits.find((rAu) => {
+ return rAu.targetId === targetId && rAu.matchedAdCount > 0;
+ });
+ if (au) {
+ multiFormatAdUnits.push(au);
+ }
+ });
+ if (multiFormatAdUnits.length > 0) {
+ const highestYield = multiFormatAdUnits.length === 1 ? multiFormatAdUnits[0] : multiFormatAdUnits.reduce((highest, cur) => {
+ const highestBid = misc.findHighestPrice(highest.ads, validatedBidType)[validatedBidType];
+ const curBid = misc.findHighestPrice(cur.ads, validatedBidType)[validatedBidType];
+ return curBid.currency === highestBid.currency && curBid.amount > highestBid.amount ? cur : highest;
+ }, multiFormatAdUnits[0]);
+ highestYield.targetId = resp.bidId;
+ highestYieldingAdUnits.push(highestYield);
+ }
+ });
+ }
+
const bidsById = bidRequest.bid.reduce((response, bid) => {
return {
...response,
@@ -374,7 +426,7 @@ export const spec = {
};
}, {});
- const hasBidAdUnits = adUnits.filter((au) => {
+ const hasBidAdUnits = highestYieldingAdUnits.filter((au) => {
const bid = bidsById[au.targetId];
if (bid && bid.bidder && BIDDER_CODE_DEAL_ALIASES.indexOf(bid.bidder) < 0) {
return au.matchedAdCount > 0;
@@ -384,7 +436,7 @@ export const spec = {
return false;
}
});
- const hasDealsAdUnits = adUnits.filter((au) => {
+ const hasDealsAdUnits = highestYieldingAdUnits.filter((au) => {
return au.deals && au.deals.length > 0;
});
diff --git a/test/spec/modules/adnuntiusBidAdapter_spec.js b/test/spec/modules/adnuntiusBidAdapter_spec.js
index d4802ffd4c0..a0846a829a8 100644
--- a/test/spec/modules/adnuntiusBidAdapter_spec.js
+++ b/test/spec/modules/adnuntiusBidAdapter_spec.js
@@ -50,7 +50,6 @@ describe('adnuntiusBidAdapter', function () {
const tzo = new Date().getTimezoneOffset();
const ENDPOINT_URL_BASE = `${URL}${tzo}&format=prebid`;
const ENDPOINT_URL = `${ENDPOINT_URL_BASE}&userId=${usi}`;
- const ENDPOINT_URL_VIDEO = `${ENDPOINT_URL_BASE}&userId=${usi}&tt=vast4`;
const ENDPOINT_URL_NOCOOKIE = `${ENDPOINT_URL_BASE}&userId=${usi}&noCookies=true`;
const ENDPOINT_URL_SEGMENTS = `${ENDPOINT_URL_BASE}&segments=segment1,segment2,segment3&userId=${usi}`;
const ENDPOINT_URL_CONSENT = `${EURO_URL}${tzo}&format=prebid&consentString=consentString&gdpr=1&userId=${usi}`;
@@ -102,7 +101,66 @@ describe('adnuntiusBidAdapter', function () {
}
},
}
- ]
+ ];
+
+ const multiBidderInResponse = {
+ bid: [{
+ bidder: 'adnuntius',
+ bidId: '3a602680158a85',
+ params: {
+ auId: '381535',
+ network: '1287',
+ bidType: 'netBid',
+ },
+ mediaTypes: {
+ banner: {
+ sizes: [[200, 200]]
+ },
+ video: {
+ playerSize: [200, 200],
+ context: 'instream'
+ }
+ }
+ },
+ {
+ bidder: 'adnuntius',
+ params: {
+ auId: '381535',
+ network: '1287',
+ bidType: 'netBid',
+ targetId: 'fred',
+ },
+ mediaTypes: {
+ banner: {
+ sizes: [[200, 200]]
+ },
+ video: {
+ playerSize: [200, 200],
+ context: 'instream'
+ }
+ }
+ }]
+ };
+
+ const multiBidderRequest = [
+ {
+ bidId: 'adn-0000000000000551',
+ bidder: 'adnuntius',
+ params: {
+ auId: '0000000000000551',
+ network: 'adnuntius',
+ },
+ mediaTypes: {
+ video: {
+ playerSize: [640, 480],
+ context: 'instream'
+ },
+ banner: {
+ sizes: [[1640, 1480], [1600, 1400]],
+ }
+ },
+ }
+ ];
const singleBidRequest = {
bid: [
@@ -184,6 +242,131 @@ describe('adnuntiusBidAdapter', function () {
}
];
+ const multiFormatServerResponse = {
+ body: {
+ 'adUnits': [
+ {
+ 'auId': '0000000000381535',
+ 'targetId': '3a602680158a85-video',
+ 'vastXml': '\n',
+ 'matchedAdCount': 1,
+ 'responseId': 'adn-rsp-453419729',
+ 'ads': [
+ {
+ 'cpm': {
+ 'amount': 1500.0,
+ 'currency': 'NOK'
+ },
+ 'bid': {
+ 'amount': 1.5,
+ 'currency': 'NOK'
+ },
+ 'grossBid': {
+ 'amount': 1.5,
+ 'currency': 'NOK'
+ },
+ 'netBid': {
+ 'amount': 1.5,
+ 'currency': 'NOK'
+ },
+ 'cost': {
+ 'amount': 1.5,
+ 'currency': 'NOK'
+ },
+ creativeWidth: 200,
+ creativeHeight: 240,
+ 'adId': 'adn-id-615465411',
+ 'vastXml': ''
+ }
+ ]
+ },
+ {
+ 'auId': '0000000000381535',
+ 'targetId': 'fred-video',
+ 'vastXml': '',
+ 'matchedAdCount': 1,
+ 'responseId': 'adn-rsp--1809523040',
+ 'ads': [
+ {
+ 'cpm': {
+ 'amount': 1500.0,
+ 'currency': 'NOK'
+ },
+ 'bid': {
+ 'amount': 1.5,
+ 'currency': 'NOK'
+ },
+ 'grossBid': {
+ 'amount': 1.5,
+ 'currency': 'NOK'
+ },
+ 'netBid': {
+ 'amount': 1.5,
+ 'currency': 'NOK'
+ },
+ 'cost': {
+ 'amount': 1.5,
+ 'currency': 'NOK'
+ },
+ creativeWidth: 200,
+ creativeHeight: 240,
+ 'adId': 'adn-id-344789675',
+ 'selectedColumn': '0',
+ 'selectedColumnPosition': '0',
+ 'vastXml': '\n',
+ }
+ ]
+ },
+ {
+ 'auId': '0000000000381535',
+ 'targetId': '3a602680158a85',
+ 'html': '\u003C!DOCTYPE html\u003E\n\n\u003C/html\u003E',
+ 'matchedAdCount': 0,
+ 'responseId': '',
+ 'ads': []
+ },
+ {
+ 'auId': '0000000000381535',
+ 'renderOption': 'DIV',
+ 'targetId': 'fred',
+ 'html': '\u003C!DOCTYPE html\u003E\n\u003C\u003E\n\u003C/html\u003E',
+ 'matchedAdCount': 1,
+ 'responseId': 'adn-rsp-1620340740',
+ 'ads': [
+ {
+ 'destinationUrlEsc': '',
+ 'cpm': {
+ 'amount': 1250.0,
+ 'currency': 'NOK'
+ },
+ creativeWidth: 200,
+ creativeHeight: 240,
+ 'bid': {
+ 'amount': 1.75,
+ 'currency': 'NOK'
+ },
+ 'grossBid': {
+ 'amount': 1.75,
+ 'currency': 'NOK'
+ },
+ 'netBid': {
+ 'amount': 1.75,
+ 'currency': 'NOK'
+ },
+ 'cost': {
+ 'amount': 1.75,
+ 'currency': 'NOK'
+ },
+ 'html': '\u003Ca \'\u003E\u003C/script\u003E',
+ }
+ ]
+ }
+ ],
+ 'network': '1287',
+ 'keywords': []
+ }
+ };
+
const serverResponse = {
body: {
'adUnits': [
@@ -565,11 +748,34 @@ describe('adnuntiusBidAdapter', function () {
it('Test Video requests', function () {
const request = spec.buildRequests(videoBidderRequest, {});
expect(request.length).to.equal(1);
+
+ const data = JSON.parse(request[0].data);
+ expect(data.adUnits.length).to.equal(1);
+ expect(data.adUnits[0].targetId).to.equal('adn-0000000000000551');
+ expect(data.adUnits[0].adType).to.equal('VAST');
+
expect(request[0]).to.have.property('bid');
const bid = request[0].bid[0]
expect(bid).to.have.property('bidId');
expect(request[0]).to.have.property('url');
- expect(request[0].url).to.equal(ENDPOINT_URL_VIDEO);
+ expect(request[0].url).to.equal(ENDPOINT_URL);
+ });
+
+ it('Test multiformat requests', function () {
+ const request = spec.buildRequests(multiBidderRequest, {});
+ expect(request.length).to.equal(1);
+ expect(request.data)
+ const data = JSON.parse(request[0].data);
+ expect(data.adUnits.length).to.equal(2);
+ expect(data.adUnits[0].targetId).to.equal('adn-0000000000000551');
+ expect(data.adUnits[0]).not.to.have.property('adType');
+ expect(data.adUnits[1].targetId).to.equal('adn-0000000000000551-video');
+ expect(data.adUnits[1].adType).to.equal('VAST');
+ expect(request[0]).to.have.property('bid');
+ const bid = request[0].bid[0]
+ expect(bid).to.have.property('bidId');
+ expect(request[0]).to.have.property('url');
+ expect(request[0].url).to.equal(ENDPOINT_URL);
});
it('should pass segments if available in config and merge from targeting', function () {
@@ -960,7 +1166,7 @@ describe('adnuntiusBidAdapter', function () {
expect(data.adUnits.length).to.equal(1);
expect(data.adUnits[0].maxDeals).to.equal(5);
});
- it('Should allow a minumum of 0 deals.', function () {
+ it('Should allow a minimum of 0 deals.', function () {
config.setBidderConfig({
bidders: ['adnuntius'],
});
@@ -1102,6 +1308,41 @@ describe('adnuntiusBidAdapter', function () {
expect(randomApiEntry.exp).to.be.greaterThan(getUnixTimestampFromNow(90));
});
+ it('should return valid response when passed valid multiformat server response', function () {
+ config.setBidderConfig({
+ bidders: ['adnuntius'],
+ config: {
+ bidType: 'netBid',
+ maxDeals: 0
+ }
+ });
+
+ const interpretedResponse = config.runWithBidder('adnuntius', () => spec.interpretResponse(multiFormatServerResponse, multiBidderInResponse));
+ expect(interpretedResponse).to.have.lengthOf(2);
+
+ let ad = multiFormatServerResponse.body.adUnits[0].ads[0];
+ expect(interpretedResponse[0].bidderCode).to.equal('adnuntius');
+ expect(interpretedResponse[0].cpm).to.equal(ad.netBid.amount * 1000);
+ expect(interpretedResponse[0].width).to.equal(Number(ad.creativeWidth));
+ expect(interpretedResponse[0].height).to.equal(Number(ad.creativeHeight));
+ expect(interpretedResponse[0].creativeId).to.equal(ad.creativeId);
+ expect(interpretedResponse[0].currency).to.equal(ad.bid.currency);
+ expect(interpretedResponse[0].netRevenue).to.equal(false);
+ expect(interpretedResponse[0].ad).to.equal(multiFormatServerResponse.body.adUnits[0].html);
+ expect(interpretedResponse[0].ttl).to.equal(360);
+
+ ad = multiFormatServerResponse.body.adUnits[3].ads[0];
+ expect(interpretedResponse[1].bidderCode).to.equal('adnuntius');
+ expect(interpretedResponse[1].cpm).to.equal(ad.netBid.amount * 1000);
+ expect(interpretedResponse[1].width).to.equal(Number(ad.creativeWidth));
+ expect(interpretedResponse[1].height).to.equal(Number(ad.creativeHeight));
+ expect(interpretedResponse[1].creativeId).to.equal(ad.creativeId);
+ expect(interpretedResponse[1].currency).to.equal(ad.bid.currency);
+ expect(interpretedResponse[1].netRevenue).to.equal(false);
+ expect(interpretedResponse[1].ad).to.equal(multiFormatServerResponse.body.adUnits[3].html);
+ expect(interpretedResponse[1].ttl).to.equal(360);
+ });
+
it('should not process valid response when passed alt bidder that is an adndeal', function () {
const altBidder = {
bid: [