Prebid - AudienceNetwork bid matching fix
6 removals
Words removed | 16 |
Total words | 1188 |
Words removed (%) | 1.35 |
318 lines
21 additions
Words added | 63 |
Total words | 1235 |
Words added (%) | 5.10 |
332 lines
/**
/**
* @file AudienceNetwork adapter.
* @file AudienceNetwork adapter.
*/
*/
import { registerBidder } from '../src/adapters/bidderFactory';
import { registerBidder } from '../src/adapters/bidderFactory';
import { formatQS } from '../src/url';
import { formatQS } from '../src/url';
import { generateUUID, getTopWindowUrl, convertTypes } from '../src/utils';
import { generateUUID, getTopWindowUrl, convertTypes } from '../src/utils';
import findIndex from 'core-js/library/fn/array/find-index';
import findIndex from 'core-js/library/fn/array/find-index';
import includes from 'core-js/library/fn/array/includes';
import includes from 'core-js/library/fn/array/includes';
const code = 'audienceNetwork';
const code = 'audienceNetwork';
const currency = 'USD';
const currency = 'USD';
const method = 'GET';
const method = 'GET';
const url = 'https://an.facebook.com/v2/placementbid.json';
const url = 'https://an.facebook.com/v2/placementbid.json';
const supportedMediaTypes = ['banner', 'video'];
const supportedMediaTypes = ['banner', 'video'];
const netRevenue = true;
const netRevenue = true;
const hbBidder = 'fan';
const hbBidder = 'fan';
const ttl = 600;
const ttl = 600;
const videoTtl = 3600;
const videoTtl = 3600;
const platver = '$prebid.version$';
const platver = '$prebid.version$';
const platform = '241394079772386';
const platform = '241394079772386';
const adapterver = '1.3.0';
const adapterver = '1.3.0';
/**
/**
* Does this bid request contain valid parameters?
* Does this bid request contain valid parameters?
* @param {Object} bid
* @param {Object} bid
* @returns {Boolean}
* @returns {Boolean}
*/
*/
const isBidRequestValid = bid =>
const isBidRequestValid = bid =>
typeof bid.params === 'object' &&
typeof bid.params === 'object' &&
typeof bid.params.placementId === 'string' &&
typeof bid.params.placementId === 'string' &&
bid.params.placementId.length > 0 &&
bid.params.placementId.length > 0 &&
Array.isArray(bid.sizes) && bid.sizes.length > 0 &&
Array.isArray(bid.sizes) && bid.sizes.length > 0 &&
(isFullWidth(bid.params.format) ? bid.sizes.map(flattenSize).some(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));
(isValidNonSizedFormat(bid.params.format) || bid.sizes.map(flattenSize).some(isValidSize));
/**
/**
* Flattens a 2-element [W, H] array as a 'WxH' string,
* Flattens a 2-element [W, H] array as a 'WxH' string,
* otherwise passes value through.
* otherwise passes value through.
* @param {Array|String} size
* @param {Array|String} size
* @returns {String}
* @returns {String}
*/
*/
const flattenSize = size =>
const flattenSize = size =>
(Array.isArray(size) && size.length === 2) ? `${size[0]}x${size[1]}` : size;
(Array.isArray(size) && size.length === 2) ? `${size[0]}x${size[1]}` : size;
/**
/**
* Expands a 'WxH' string as a 2-element [W, H] array
* Expands a 'WxH' string as a 2-element [W, H] array
* @param {String} size
* @param {String} size
* @returns {Array}
* @returns {Array}
*/
*/
const expandSize = size => size.split('x').map(Number);
const expandSize = size => size.split('x').map(Number);
/**
/**
* Is this a valid slot size?
* Is this a valid slot size?
* @param {String} size
* @param {String} size
* @returns {Boolean}
* @returns {Boolean}
*/
*/
const isValidSize = size => includes(['300x250', '320x50'], size);
const isValidSize = size => includes(['300x250', '320x50'], size);
/**
/**
* Is this a valid, non-sized format?
* Is this a valid, non-sized format?
* @param {String} size
* @param {String} size
* @returns {Boolean}
* @returns {Boolean}
*/
*/
const isValidNonSizedFormat = format => includes(['video', 'native'], format);
const isValidNonSizedFormat = format => includes(['video', 'native'], format);
/**
/**
* Is this a valid size and format?
* Is this a valid size and format?
* @param {String} size
* @param {String} size
* @returns {Boolean}
* @returns {Boolean}
*/
*/
const isValidSizeAndFormat = (size, format) =>
const isValidSizeAndFormat = (size, format) =>
(isFullWidth(format) && flattenSize(size) === '300x250') ||
(isFullWidth(format) && flattenSize(size) === '300x250') ||
isValidNonSizedFormat(format) ||
isValidNonSizedFormat(format) ||
isValidSize(flattenSize(size));
isValidSize(flattenSize(size));
/**
/**
* Find a preferred entry, if any, from an array of valid sizes.
* Find a preferred entry, if any, from an array of valid sizes.
* @param {Array<String>} acc
* @param {Array<String>} acc
* @param {String} cur
* @param {String} cur
*/
*/
const sortByPreferredSize = (acc, cur) =>
const sortByPreferredSize = (acc, cur) =>
(cur === '300x250') ? [cur, ...acc] : [...acc, cur];
(cur === '300x250') ? [cur, ...acc] : [...acc, cur];
/**
/**
* Map any deprecated size/formats to new values.
* Map any deprecated size/formats to new values.
* @param {String} size
* @param {String} size
* @param {String} format
* @param {String} format
*/
*/
const mapDeprecatedSizeAndFormat = (size, format) =>
const mapDeprecatedSizeAndFormat = (size, format) =>
isFullWidth(format) ? ['300x250', null] : [size, format];
isFullWidth(format) ? ['300x250', null] : [size, format];
/**
/**
* Is this a video format?
* Is this a video format?
* @param {String} format
* @param {String} format
* @returns {Boolean}
* @returns {Boolean}
*/
*/
const isVideo = format => format === 'video';
const isVideo = format => format === 'video';
/**
/**
* Is this a fullwidth format?
* Is this a fullwidth format?
* @param {String} format
* @param {String} format
* @returns {Boolean}
* @returns {Boolean}
*/
*/
const isFullWidth = format => format === 'fullwidth';
const isFullWidth = format => format === 'fullwidth';
/**
/**
* Which SDK version should be used for this format?
* Which SDK version should be used for this format?
* @param {String} format
* @param {String} format
* @returns {String}
* @returns {String}
*/
*/
const sdkVersion = format => isVideo(format) ? '' : '6.0.web';
const sdkVersion = format => isVideo(format) ? '' : '6.0.web';
/**
/**
* Which platform identifier should be used?
* Which platform identifier should be used?
* @param {Array<String>} platforms Possible platform identifiers
* @param {Array<String>} platforms Possible platform identifiers
* @returns {String} First valid platform found, or default if none found
* @returns {String} First valid platform found, or default if none found
*/
*/
const findPlatform = platforms => [...platforms.filter(Boolean), platform][0];
const findPlatform = platforms => [...platforms.filter(Boolean), platform][0];
/**
/**
* Does the search part of the URL contain "anhb_testmode"
* Does the search part of the URL contain "anhb_testmode"
* and therefore indicate testmode should be used?
* and therefore indicate testmode should be used?
* @returns {String} "true" or "false"
* @returns {String} "true" or "false"
*/
*/
const isTestmode = () => Boolean(
const isTestmode = () => Boolean(
window && window.location &&
window && window.location &&
typeof window.location.search === 'string' &&
typeof window.location.search === 'string' &&
window.location.search.indexOf('anhb_testmode') !== -1
window.location.search.indexOf('anhb_testmode') !== -1
).toString();
).toString();
/**
/**
* Generate ad HTML for injection into an iframe
* Generate ad HTML for injection into an iframe
* @param {String} placementId
* @param {String} placementId
* @param {String} format
* @param {String} format
* @param {String} bidId
* @param {String} bidId
* @returns {String} HTML
* @returns {String} HTML
*/
*/
const createAdHtml = (placementId, format, bidId) => {
const createAdHtml = (placementId, format, bidId) => {
const nativeStyle = format === 'native' ? '<script>window.onload=function(){if(parent){var o=document.getElementsByTagName("head")[0];var s=parent.document.getElementsByTagName("style");for(var i=0;i<s.length;i++)o.appendChild(s[i].cloneNode(true));}}</script>' : '';
const nativeStyle = format === 'native' ? '<script>window.onload=function(){if(parent){var o=document.getElementsByTagName("head")[0];var s=parent.document.getElementsByTagName("style");for(var i=0;i<s.length;i++)o.appendChild(s[i].cloneNode(true));}}</script>' : '';
const nativeContainer = format === 'native' ? '<div class="thirdPartyRoot"><a class="fbAdLink"><div class="fbAdMedia thirdPartyMediaClass"></div><div class="fbAdSubtitle thirdPartySubtitleClass"></div><div class="fbDefaultNativeAdWrapper"><div class="fbAdCallToAction thirdPartyCallToActionClass"></div><div class="fbAdTitle thirdPartyTitleClass"></div></div></a></div>' : '';
const nativeContainer = format === 'native' ? '<div class="thirdPartyRoot"><a class="fbAdLink"><div class="fbAdMedia thirdPartyMediaClass"></div><div class="fbAdSubtitle thirdPartySubtitleClass"></div><div class="fbDefaultNativeAdWrapper"><div class="fbAdCallToAction thirdPartyCallToActionClass"></div><div class="fbAdTitle thirdPartyTitleClass"></div></div></a></div>' : '';
return `<html>
return `<html>
<head>${nativeStyle}</head>
<head>${nativeStyle}</head>
<body>
<body>
<div style="display:none;position:relative;">
<div style="display:none;position:relative;">
<script type="text/javascript" src="https://connect.facebook.net/en_US/fbadnw60-tag.js" async></script>
<script type="text/javascript" src="https://connect.facebook.net/en_US/fbadnw60-tag.js" async></script>
<script type="text/javascript">
<script type="text/javascript">
window.ADNW = window.ADNW || {};
window.ADNW = window.ADNW || {};
window.ADNW.v60 = window.ADNW.v60 || {};
window.ADNW.v60 = window.ADNW.v60 || {};
window.ADNW.v60.slots = window.ADNW.v60.slots || [];
window.ADNW.v60.slots = window.ADNW.v60.slots || [];
window.ADNW.v60.slots.push({
window.ADNW.v60.slots.push({
rootElement: document.currentScript.parentElement,
rootElement: document.currentScript.parentElement,
placementid: '${placementId}',
placementid: '${placementId}',
format: '${format}',
format: '${format}',
bidid: '${bidId}',
bidid: '${bidId}',
testmode: false,
testmode: false,
onAdLoaded: function(rootElement) {
onAdLoaded: function(rootElement) {
console.log('Audience Network [${placementId}] ad loaded');
console.log('Audience Network [${placementId}] ad loaded');
rootElement.style.display = 'block';
rootElement.style.display = 'block';
},
},
onAdError: function(errorCode, errorMessage) {
onAdError: function(errorCode, errorMessage) {
console.log('Audience Network [${placementId}] error (' + errorCode + ') ' + errorMessage);
console.log('Audience Network [${placementId}] error (' + errorCode + ') ' + errorMessage);
}
}
});
});
</script>
</script>
${nativeContainer}
${nativeContainer}
</div>
</div>
</body>
</body>
</html>`;
</html>`;
};
};
/**
/**
* Get the current window location URL correctly encoded for use in a URL query string.
* Get the current window location URL correctly encoded for use in a URL query string.
* @returns {String} URI-encoded URL
* @returns {String} URI-encoded URL
*/
*/
const getTopWindowUrlEncoded = () => encodeURIComponent(getTopWindowUrl());
const getTopWindowUrlEncoded = () => encodeURIComponent(getTopWindowUrl());
/**
/**
* Convert each bid request to a single URL to fetch those bids.
* Convert each bid request to a single URL to fetch those bids.
* @param {Array} bids - list of bids
* @param {Array} bids - list of bids
* @param {String} bids[].placementCode - Prebid placement identifier
* @param {String} bids[].placementCode - Prebid placement identifier
* @param {Object} bids[].params
* @param {Object} bids[].params
* @param {String} bids[].params.placementId - Audience Network placement identifier
* @param {String} bids[].params.placementId - Audience Network placement identifier
* @param {String} bids[].params.platform - Audience Network platform identifier (optional)
* @param {String} bids[].params.platform - Audience Network platform identifier (optional)
* @param {String} bids[].params.format - Optional format, one of 'video' or 'native' if set
* @param {String} bids[].params.format - Optional format, one of 'video' or 'native' if set
* @param {Array} bids[].sizes - list of desired advert sizes
* @param {Array} bids[].sizes - list of desired advert sizes
* @param {Array} bids[].sizes[] - Size arrays [h,w]: should include one of [300, 250], [320, 50]
* @param {Array} bids[].sizes[] - Size arrays [h,w]: should include one of [300, 250], [320, 50]
* @returns {Array<Object>} List of URLs to fetch, plus formats and sizes for later use with interpretResponse
* @returns {Array<Object>} List of URLs to fetch, plus formats and sizes for later use with interpretResponse
*/
*/
const buildRequests = bids => {
const buildRequests = bids => {
// Build lists of placementids, adformats, sizes and SDK versions
// Build lists of placementids, adformats, sizes and SDK versions
const placementids = [];
const placementids = [];
const adformats = [];
const adformats = [];
const sizes = [];
const sizes = [];
const sdk = [];
const sdk = [];
const platforms = [];
const platforms = [];
const requestIds = [];
const requestIds = [];
const placementRequests = {};
bids.forEach(bid => bid.sizes
bids.forEach(bid => bid.sizes
.map(flattenSize)
.map(flattenSize)
.filter(size => isValidSizeAndFormat(size, bid.params.format))
.filter(size => isValidSizeAndFormat(size, bid.params.format))
.reduce(sortByPreferredSize, [])
.reduce(sortByPreferredSize, [])
.slice(0, 1)
.slice(0, 1)
.forEach(preferredSize => {
.forEach(preferredSize => {
const [size, format] = mapDeprecatedSizeAndFormat(preferredSize, bid.params.format);
const [size, format] = mapDeprecatedSizeAndFormat(preferredSize, bid.params.format);
placementids.push(bid.params.placementId);
placementids.push(bid.params.placementId);
adformats.push(format || size);
adformats.push(format || size);
sizes.push(size);
sizes.push(size);
sdk.push(sdkVersion(format));
sdk.push(sdkVersion(format));
platforms.push(bid.params.platform);
platforms.push(bid.params.platform);
requestIds.push(bid.bidId);
requestIds.push(bid.bidId);
placementRequests[bid.params.placementId] = placementRequests[bid.params.placementId] || [];
placementRequests[bid.params.placementId].push({
placementId: bid.params.placementId,
format: (format || size),
size: size,
requestId: bid.bidId,
bidRequest: bid
});
})
})
);
);
// Build URL
// Build URL
const testmode = isTestmode();
const testmode = isTestmode();
const pageurl = getTopWindowUrlEncoded();
const pageurl = getTopWindowUrlEncoded();
const platform = findPlatform(platforms);
const platform = findPlatform(platforms);
const cb = generateUUID();
const cb = generateUUID();
const search = {
const search = {
placementids,
placementids,
adformats,
adformats,
testmode,
testmode,
pageurl,
pageurl,
sdk,
sdk,
adapterver,
adapterver,
platform,
platform,
platver,
platver,
cb
cb
};
};
const video = findIndex(adformats, isVideo);
const video = findIndex(adformats, isVideo);
if (video !== -1) {
if (video !== -1) {
[search.playerwidth, search.playerheight] = expandSize(sizes[video]);
[search.playerwidth, search.playerheight] = expandSize(sizes[video]);
}
}
const data = formatQS(search);
const data = formatQS(search);
return [{ adformats, data, method, requestIds, sizes, url }];
return [{ method, url, data, placementRequests }];
};
};
/**
/**
* Convert a server response to a bid response.
* Convert a server response to a bid response.
* @param {Object} response - object representing the response
* @param {Object} response - object representing the response
* @param {Object} response.body - response body, already converted from JSON
* @param {Object} response.body - response body, already converted from JSON
* @param {Object} bidRequests - the original bid requests
* @param {Object} bidRequests - the original bid requests
* @param {Array} bidRequest.adformats - list of formats for the original bid requests
* @param {Array} bidRequest.adformats - list of formats for the original bid requests
* @param {Array} bidRequest.sizes - list of sizes fot the original bid requests
* @param {Array} bidRequest.sizes - list of sizes fot the original bid requests
* @returns {Array<Object>} A list of bid response objects
* @returns {Array<Object>} A list of bid response objects
*/
*/
const interpretResponse = ({ body }, { adformats, requestIds, sizes }) => {
const interpretResponse = ({ body }, { placementRequests }) => {
const { bids = {} } = body;
const { bids = {} } = body;
return Object.keys(bids)
return Object.keys(bids)
// extract Array of bid responses
// extract Array of bid responses
.map(placementId => bids[placementId])
.map(placementId => bids[placementId])
// flatten
// flatten
.reduce((a, b) => a.concat(b), [])
.reduce((a, b) => a.concat(b), [])
// transform to bidResponse
// transform to bidResponse
.map((bid, i) => {
.map((bid, i) => {
const {
const {
bid_id: fbBidid,
bid_id: fbBidid,
placement_id: creativeId,
placement_id: creativeId,
bid_price_cents: cpm
bid_price_cents: cpm
} = bid;
} = bid;
const format = adformats[i];
if (!placementRequests[bid.placement_id] || !placementRequests[bid.placement_id].length) {
const [width, height] = expandSize(flattenSize(sizes[i]));
return undefined; // gets filtered out before returning responses
}
let requestData = placementRequests[bid.placement_id].shift();
const format = requestData['format'];
const [width, height] = expandSize(flattenSize(requestData['size']));
const requestId = requestData['requestId'];
const ad = createAdHtml(creativeId, format, fbBidid);
const ad = createAdHtml(creativeId, format, fbBidid);
const requestId = requestIds[i];
const bidResponse = {
const bidResponse = {
// Prebid attributes
// Prebid attributes
requestId,
requestId,
cpm: cpm / 100,
cpm: cpm / 100,
width,
width,
height,
height,
ad,
ad,
ttl,
ttl,
creativeId,
creativeId,
netRevenue,
netRevenue,
currency,
currency,
// Audience Network attributes
// Audience Network attributes
hb_bidder: hbBidder,
hb_bidder: hbBidder,
fb_bidid: fbBidid,
fb_bidid: fbBidid,
fb_format: format,
fb_format: format,
fb_placementid: creativeId
fb_placementid: creativeId
};
};
// Video attributes
// Video attributes
if (isVideo(format)) {
if (isVideo(format)) {
const pageurl = getTopWindowUrlEncoded();
const pageurl = getTopWindowUrlEncoded();
bidResponse.mediaType = 'video';
bidResponse.mediaType = 'video';
bidResponse.vastUrl = `https://an.facebook.com/v1/instream/vast.xml?placementid=${creativeId}&pageurl=${pageurl}&playerwidth=${width}&playerheight=${height}&bidid=${fbBidid}`;
bidResponse.vastUrl = `https://an.facebook.com/v1/instream/vast.xml?placementid=${creativeId}&pageurl=${pageurl}&playerwidth=${width}&playerheight=${height}&bidid=${fbBidid}`;
bidResponse.ttl = videoTtl;
bidResponse.ttl = videoTtl;
}
}
return bidResponse;
return bidResponse;
});
})
.filter(a => a); // filter out any undefined responses
};
};
/**
/**
* Covert bid param types for S2S
* Covert bid param types for S2S
* @param {Object} params bid params
* @param {Object} params bid params
* @param {Boolean} isOpenRtb boolean to check openrtb2 protocol
* @param {Boolean} isOpenRtb boolean to check openrtb2 protocol
* @return {Object} params bid params
* @return {Object} params bid params
*/
*/
const transformBidParams = (params, isOpenRtb) => {
const transformBidParams = (params, isOpenRtb) => {
return convertTypes({
return convertTypes({
'placementId': 'string'
'placementId': 'string'
}, params);
}, params);
}
}
export const spec = {
export const spec = {
code,
code,
supportedMediaTypes,
supportedMediaTypes,
isBidRequestValid,
isBidRequestValid,
buildRequests,
buildRequests,
interpretResponse,
interpretResponse,
transformBidParams
transformBidParams
};
};
registerBidder(spec);
registerBidder(spec);