Skip to content
This repository has been archived by the owner on Feb 13, 2021. It is now read-only.

Release/1.17.0 #1

Merged
merged 18 commits into from
May 22, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
AOL Prebid 1.17.0
----------------
Added functionality for rendering pixels once.


AOL Prebid 1.16.0
----------------
Added Audience Network adapter.
Added creative key field in bid response for Sharethrough adapter.


AOL Prebid 1.15.0
----------------
Nexage API implemented.
Expand Down
1 change: 1 addition & 0 deletions adapters.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"aol",
"appnexus",
"appnexusAst",
"audienceNetwork",
"conversant",
"districtmDMX",
"fidelity",
Expand Down
83 changes: 83 additions & 0 deletions integrationExamples/gpt/audienceNetwork_dfp.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<html>
<head>
<script src="/build/dev/prebid.js" async></script>
<script>
var PREBID_TIMEOUT = 2000;
var adUnits = [{
code: '/5555555/hb_300x250',
sizes: [[300, 250]],
bids: [{
bidder: 'audienceNetwork',
params: {
placementId: '555555555555555_555555555555555'
}
}]
}];

(function () {
var gads = document.createElement('script');
gads.async = true;
gads.type = 'text/javascript';
var useSSL = 'https:' == document.location.protocol;
gads.src = (useSSL ? 'https:' : 'http:') + '//www.googletagservices.com/tag/js/gpt.js';
var node = document.getElementsByTagName('script')[0];
node.parentNode.insertBefore(gads, node);
})();

var googletag = googletag || {};
googletag.cmd = googletag.cmd || [];
googletag.cmd.push(function() {
googletag.pubads().disableInitialLoad();
});
googletag.cmd.push(function () {
googletag.defineSlot('/5555555/hb_300x250', [[300, 250]], 'div-gpt-ad-5555555555555-0').addService(googletag.pubads());
googletag.pubads().enableSingleRequest();
googletag.enableServices();
});

var sendAdserverRequest = function () {
if (pbjs.adserverRequestSent) return;
pbjs.adserverRequestSent = true;
googletag.cmd.push(function() {
pbjs.que.push(function() {
pbjs.setTargetingForGPTAsync();
googletag.pubads().refresh();
});
});
};

setTimeout(function() {
sendAdserverRequest();
}, PREBID_TIMEOUT);

var pbjs = pbjs || {};
pbjs.que = pbjs.que || [];

pbjs.que.push(function() {
pbjs.addAdUnits(adUnits);
pbjs.requestBids({
bidsBackHandler: sendAdserverRequest
});
});
</script>
</head>
<body>
<h2>Prebid.js Test</h2>
<div id='div-gpt-ad-5555555555555-0'>
<script>
googletag.cmd.push(function() {
googletag.display('div-gpt-ad-5555555555555-0');
});
</script>
</div>
<div>
<p>Audience Network quick start</p>
<ol>
<li>Create a new App at <a href="https://developers.facebook.com/apps">https://developers.facebook.com/apps</a></li>
<li>Add the Audience Network product to it</li>
<li>Create a new Placement to generate your placementId</li>
<li>To test, ensure the User-Agent request header represents a mobile device</li>
</ol>
</div>
</body>
</html>
3 changes: 2 additions & 1 deletion src/adapters/analytics/aolPartnersIds.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,6 @@
"twenga": 54,
"lifestreet": 55,
"vertamedia": 56,
"stickyadstv": 57
"stickyadstv": 57,
"audienceNetwork": 58
}
17 changes: 14 additions & 3 deletions src/adapters/aol.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ const bidmanager = require('../bidmanager.js');
const constants = require('../constants.json');
const events = require('src/events');

$$PREBID_GLOBAL$$.aolGlobals = {
pixelsDropped: false
};

const AolAdapter = function AolAdapter() {

let showCpmAdjustmentWarning = true;
Expand Down Expand Up @@ -67,8 +71,11 @@ const AolAdapter = function AolAdapter() {
})();

function dropSyncCookies(pixels) {
let pixelElements = parsePixelItems(pixels);
renderPixelElements(pixelElements);
if (!$$PREBID_GLOBAL$$.aolGlobals.pixelsDropped) {
let pixelElements = parsePixelItems(pixels);
renderPixelElements(pixelElements);
$$PREBID_GLOBAL$$.aolGlobals.pixelsDropped = true;
}
}

function parsePixelItems(pixels) {
Expand Down Expand Up @@ -227,7 +234,11 @@ const AolAdapter = function AolAdapter() {
if (bid.params.userSyncOn === constants.EVENTS.BID_RESPONSE) {
dropSyncCookies(response.ext.pixels);
} else {
ad += response.ext.pixels;
let formattedPixels = response.ext.pixels.replace(/<\/?script( type=('|")text\/javascript('|")|)?>/g, '');

ad += '<script>if(!parent.$$PREBID_GLOBAL$$.aolGlobals.pixelsDropped){' +
'parent.$$PREBID_GLOBAL$$.aolGlobals.pixelsDropped=true;' + formattedPixels +
'}</script>';
}
}

Expand Down
208 changes: 208 additions & 0 deletions src/adapters/audienceNetwork.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
/**
* @file AudienceNetwork adapter.
*/
import { ajax } from '../ajax';
import { createBid } from '../bidfactory';
import { addBidResponse } from '../bidmanager';
import { STATUS } from '../constants.json';
import { format } from '../url';
import { logError } from '../utils';
import { createNew } from './adapter';

const baseAdapter = createNew('audienceNetwork');
const setBidderCode = baseAdapter.setBidderCode;
const getBidderCode = baseAdapter.getBidderCode;

/**
* Does this bid request contain valid parameters?
* @param {Object} bid
* @returns {Boolean}
*/
const validateBidRequest = bid =>
typeof bid.params === 'object' &&
typeof bid.params.placementId === 'string' &&
bid.params.placementId.length > 0 &&
Array.isArray(bid.sizes) && bid.sizes.length > 0;

/**
* Does this bid request contain valid sizes?
* @param {Object} bid
* @returns {Boolean}
*/
const validateBidRequestSizes = bid => {
bid.sizes = bid.sizes.map(flattenSize);
return bid.sizes.every( size =>
['native', 'fullwidth', '300x250', '320x50'].includes(size) );
};

/**
* Flattens a 2-element [W, H] array as a 'WxH' string,
* otherwise passes value through.
* @params {Array|String} size
* @returns {String}
*/
const flattenSize = size =>
(Array.isArray(size) && size.length === 2) ? `${size[0]}x${size[1]}` : size;

/**
* Does the search part of the URL contain "anhb_testmode"
* and therefore indicate testmode should be used?
* @returns {String} "true" or "false"
*/
const isTestmode = () => Boolean(
window && window.location &&
typeof window.location.search === 'string' &&
window.location.search.indexOf('anhb_testmode') !== -1
).toString();

/**
* Parse JSON-as-string into an Object, default to empty.
* @param {String} JSON-as-string
* @returns {Object}
*/
const parseJson = jsonAsString => {
let data = {};
try {
data = JSON.parse(jsonAsString);
} catch (err) {}
return data;
};

/**
* Is this a native advert size?
* @param {String} size
* @returns {Boolean}
*/
const isNative = (size) => ['native', 'fullwidth'].includes(size);

/**
* Generate ad HTML for injection into an iframe
* @param {String} placementId
* @param {String} size
* @param {String} bidId
* @returns {String} HTML
*/
const createAdHtml = (placementId, size, bidId) => {
const nativeStyle = isNative(size) ? '<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 = isNative(size) ? '<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><head>${nativeStyle}</head><body><div style="display:none;position:relative;">
<script>var data = {placementid:'${placementId}',format:'${size}',bidid:'${bidId}',onAdLoaded:function(e){e.style.display = 'block';},onAdError:function(c,m){console.log('Audience Network error (' + c + ') ' + m);}};
(function(a,b,c){var d='https://www.facebook.com',e='https://connect.facebook.net/en_US/fbadnw55.js',f={iframeLoaded:true,xhrLoaded:true},g=5,h=a.data,i=0,j=function(ea){if(ea==null)throw new Error();return ea;},k=function(ea){if(ea instanceof HTMLElement)return ea;throw new Error();},l=function(){if(Date.now){return Date.now();}else return +new Date();},m=function(ea){if(++i>g)return;var fa=d+'/audience_network/client_event',ga={cb:l(),event_name:'ADNW_ADERROR',ad_pivot_type:'audience_network_mobile_web',sdk_version:'5.5.web',app_id:h.placementid.split('_')[0],publisher_id:h.placementid.split('_')[1],error_message:ea},ha=[];for(var ia in ga)ha.push(encodeURIComponent(ia)+'='+encodeURIComponent(ga[ia]));var ja=fa+'?'+ha.join('&'),ka=new XMLHttpRequest();ka.open('GET',ja,true);ka.send();},n=function(){if(b.currentScript){return b.currentScript;}else{var ea=b.getElementsByTagName('script');return ea[ea.length-1];}},o=function(ea){try{return ea.document.referrer;}catch(fa){}return '';},p=function(){var ea=a;try{while(ea!=ea.parent){ea.parent.origin;ea=ea.parent;}}catch(fa){}return ea;},q=function(ea){var fa=ea.indexOf('/',ea.indexOf('://')+3);if(fa===-1)return ea;return ea.substring(0,fa);},r=function(ea){return ea.location.href||o(ea);},s=function(ea,fa){if(ea.sdkLoaded)return;var ga=fa.createElement('iframe');ga.name='fbadnw';ga.style.display='none';j(fa.body).appendChild(ga);ga.contentWindow.addEventListener('error',function(event){m(event.message);},false);var ha=ga.contentDocument.createElement('script');ha.src=e;ha.async=true;j(ga.contentDocument.body).appendChild(ha);ea.sdkLoaded=true;},t=function(ea){var fa=/^https?:\\/\\/www\\.google(\\.com?)?.\\w{2,3}$/;return !!ea.match(fa);},u=function(ea){return ea.endsWith('cdn.ampproject.org');},v=function(){var ea=c.ancestorOrigins||[],fa=ea[ea.length-1]||c.origin,ga=ea[ea.length-2]||c.origin;if(t(fa)&&u(ga)){return q(ga);}else return q(fa);},w=function(ea){try{return JSON.parse(ea);}catch(fa){m(fa.message);return null;}},x=function(ea,fa,ga){if(!ea.iframe){var ha=ga.createElement('iframe');ha.src=d+'/audiencenetwork/iframe/';ha.style.display='none';j(ga.body).appendChild(ha);ea.iframe=ha;ea.iframeAppendedTime=l();ea.iframeData={};}fa.iframe=j(ea.iframe);fa.iframeData=ea.iframeData;fa.tagJsIframeAppendedTime=ea.iframeAppendedTime||0;},y=function(ea){var fa=d+'/audiencenetwork/xhr/?sdk=5.5.web';for(var ga in ea)if(typeof ea[ga]!=='function')fa+='&'+ga+'='+encodeURIComponent(ea[ga]);var ha=new XMLHttpRequest();ha.open('GET',fa,true);ha.withCredentials=true;ha.onreadystatechange=function(){if(ha.readyState===4){var ia=w(ha.response);if(ia)ea.events.push({name:'xhrLoaded',source:ea.iframe.contentWindow,data:ia,postMessageTimestamp:l(),receivedTimestamp:l()});}};ha.send();},z=function(ea,fa){var ga=d+'/audiencenetwork/xhriframe/?sdk=5.5.web';for(var ha in fa)if(typeof fa[ha]!=='function')ga+='&'+ha+'='+encodeURIComponent(fa[ha]);var ia=b.createElement('iframe');ia.src=ga;ia.style.display='none';j(b.body).appendChild(ia);fa.iframe=ia;fa.iframeData={};fa.tagJsIframeAppendedTime=l();},aa=function(ea){var fa=function(event){try{var ia=event.data;if(ia.name in f)ea.events.push({name:ia.name,source:event.source,data:ia.data});}catch(ha){}},ga=j(ea.iframe).contentWindow.parent;ga.addEventListener('message',fa,false);},ba=function(ea){if(ea.context)return true;try{return !!JSON.parse(decodeURI(ea.name)).ampcontextVersion;}catch(fa){return false;}},ca=function(ea){var fa=l(),ga=p(),ha=k(n().parentElement),ia=ga!=a.top,ja=ga.$sf&&ga.$sf.ext,ka=r(ga);ga.ADNW=ga.ADNW||{};ga.ADNW.v55=ga.ADNW.v55||{ads:[]};var la=ga.ADNW.v55;s(la,ga.document);var ma={amp:ba(ga),events:[],tagJsInitTime:fa,rootElement:ha,iframe:null,tagJsIframeAppendedTime:la.iframeAppendedTime||0,url:ka,domain:v(),channel:q(r(ga)),width:screen.width,height:screen.height,pixelratio:a.devicePixelRatio,placementindex:la.ads.length,crossdomain:ia,safeframe:!!ja,placementid:h.placementid,format:h.format||'300x250',testmode:!!h.testmode,onAdLoaded:h.onAdLoaded,onAdError:h.onAdError};if(h.bidid)ma.bidid=h.bidid;if(ia){z(la,ma);}else{x(la,ma,ga.document);y(ma);}aa(ma);ma.rootElement.dataset.placementid=ma.placementid;la.ads.push(ma);};try{ca();}catch(da){m(da.message||da);throw da;}})(window,document,location);
</script>
${nativeContainer}</div></body></html>`;
};

/**
* Creates a "good" Bid object with the given bid ID and CPM.
* @param {String} placementId
* @param {String} bidId
* @param {String} size
* @param {Number} cpmCents
* @returns {Object} Bid
*/
const createSuccessBidResponse = (placementId, size, bidId, cpmCents) => {
const bid = createBid(STATUS.GOOD, { bidId });
// Prebid attributes
bid.bidderCode = getBidderCode();
bid.cpm = cpmCents / 100;
bid.ad = createAdHtml(placementId, size, bidId);
if (!isNative(size)) {
[bid.width, bid.height] = size.split('x').map(Number);
}
// Audience Network attributes
bid.hb_bidder = 'fan';
bid.fb_bidid = bidId;
bid.fb_format = size;
bid.fb_placementid = placementId;
return bid;
};

/**
* Creates a "no bid" Bid object.
* @returns {Object} Bid
*/
const createFailureBidResponse = () => {
const bid = createBid(STATUS.NO_BID);
bid.bidderCode = getBidderCode();
return bid;
};

/**
* Fetch bids for given parameters.
* @param {Object} bidRequest
* @param {Array} params.bids - list of bids
* @param {String} params.bids[].placementCode - Prebid placement identifier
* @param {Object} params.bids[].params
* @param {String} params.bids[].params.placementId - Audience Network placement identifier
* @param {Array} params.bids[].sizes - list of accepted advert sizes
* @param {Array|String} params.bids[].sizes[] - one of 'native', '300x250', '300x50', [300, 250], [300, 50]
* @returns {void}
*/
const callBids = bidRequest => {
// Build lists of adUnitCodes, placementids and adformats
const adUnitCodes = [];
const placementids = [];
const adformats = [];
bidRequest.bids
.filter(validateBidRequest)
.filter(validateBidRequestSizes)
.forEach( bid => bid.sizes.forEach( size => {
adUnitCodes.push(bid.placementCode);
placementids.push(bid.params.placementId);
adformats.push(size);
}));

if (placementids.length) {
// Build URL
const testmode = isTestmode();
const url = format({
protocol: 'https',
host: 'an.facebook.com',
pathname: '/v2/placementbid.json',
search: {
sdk: '5.5.web',
testmode,
placementids,
adformats
}
});
// Request
ajax(url, res => {
// Handle response
const data = parseJson(res);
if (data.errors && data.errors.length) {
const noBid = createFailureBidResponse();
adUnitCodes.forEach( adUnitCode => addBidResponse(adUnitCode, noBid) );
data.errors.forEach(logError);
} else {
// For each placementId in bids Object
Object.keys(data.bids)
// extract Array of bid responses
.map( placementId => data.bids[placementId] )
// flatten
.reduce( (a, b) => a.concat(b), [] )
// call addBidResponse
.forEach( (bid, i) =>
addBidResponse(adUnitCodes[i], createSuccessBidResponse(
bid.placement_id, adformats[i], bid.bid_id, bid.bid_price_cents
))
);
}
}, null, { withCredentials: true });
} else {
// No valid bids
logError('No valid bids requested');
}
};

/**
* @class AudienceNetwork
* @type {Object}
* @property {Function} callBids - fetch bids for given parameters
* @property {Function} setBidderCode - used for bidder aliasing
* @property {Function} getBidderCode - unique 'audienceNetwork' identifier
*/
const AudienceNetwork = () => {
return { callBids, setBidderCode, getBidderCode };
};
module.exports = AudienceNetwork;
Loading