This repository has been archived by the owner on Feb 13, 2021. It is now read-only.
forked from prebid/Prebid.js
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request prebid#87 in AOLP_ADS_JS/prebid.js from contrib/sy…
…nc-with-aol-github to aolgithub-master * commit '0c6c83cc2bfaeeafa9da967c90d83da4af58e7b3': Add change log entry. Renaming and fix missing semicolon. Update parsing script tag regexp. Refactoring. Replace stub bids objects, add additional case for rendering pixels. Refactoring. Replace test consts by methods. Create separate namespace for aol globals, add unit tests. Fix unit tests, remove unnecessary spaces. Implement render pixels only once feature. Add change log entry. Add reporting ID for analytics. Add Facebook Audience Network adapter fix specs update version fix jslint errors Change to using a closure for the callback from ajax to preserve bidObj Ensure cookies get sent in request headers (prebid#1069) Rz/ajax (#5) (prebid#1042)
- Loading branch information
Showing
11 changed files
with
833 additions
and
292 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,6 +10,7 @@ | |
"aol", | ||
"appnexus", | ||
"appnexusAst", | ||
"audienceNetwork", | ||
"conversant", | ||
"districtmDMX", | ||
"fidelity", | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -55,5 +55,6 @@ | |
"twenga": 54, | ||
"lifestreet": 55, | ||
"vertamedia": 56, | ||
"stickyadstv": 57 | ||
"stickyadstv": 57, | ||
"audienceNetwork": 58 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
Oops, something went wrong.