Skip to content

Commit

Permalink
freewheelSSPBidAdapter (#4645)
Browse files Browse the repository at this point in the history
* freewheelSSPBidAdapter Update the freewheelSSPBidAdapter based on Prebid 3.0 changes, add max and min size limit as per client's requirement

* freewheelSSPBidAdapter fix the path issue

* freewheelSSPBidAdapter fix the indentation

* update the test

* rename freewheel bid adapter name

* freewheelSSPBidAdapter use bidderRequest.refererInfo instead of utils.getTopWindowUrl(), remove the unused code

* freewheelSSPBidAdapter add spec file

* freewheelSSPBidAdapter update the gdpr and refererInfo condition check
  • Loading branch information
xwang202 authored and jsnellbaker committed Jan 3, 2020
1 parent 28c6dc5 commit c015d79
Show file tree
Hide file tree
Showing 3 changed files with 608 additions and 3 deletions.
371 changes: 371 additions & 0 deletions modules/freewheel-sspBidAdapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,371 @@
import * as utils from '../src/utils';
import { registerBidder } from '../src/adapters/bidderFactory';

const BIDDER_CODE = 'freewheel-ssp';

const PROTOCOL = getProtocol();
const FREEWHEEL_ADSSETUP = PROTOCOL + '://ads.stickyadstv.com/www/delivery/swfIndex.php';
const MUSTANG_URL = PROTOCOL + '://cdn.stickyadstv.com/mustang/mustang.min.js';
const PRIMETIME_URL = PROTOCOL + '://cdn.stickyadstv.com/prime-time/';
const USER_SYNC_URL = PROTOCOL + '://ads.stickyadstv.com/auto-user-sync';

function getProtocol() {
return 'https';
}

function isValidUrl(str) {
if (!str) {
return false;
}

// regExp for url validation
var pattern = /^(https?|ftp|file):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/;
return pattern.test(str);
}

function getBiggerSize(array) {
var result = [0, 0];
for (var i = 0; i < array.length; i++) {
if (array[i][0] * array[i][1] > result[0] * result[1]) {
result = array[i];
}
}
return result;
}

function getBiggerSizeWithLimit(array, minSizeLimit, maxSizeLimit) {
var minSize = minSizeLimit || [0, 0];
var maxSize = maxSizeLimit || [Number.MAX_VALUE, Number.MAX_VALUE];
var candidates = [];

for (var i = 0; i < array.length; i++) {
if (array[i][0] * array[i][1] >= minSize[0] * minSize[1] && array[i][0] * array[i][1] <= maxSize[0] * maxSize[1]) {
candidates.push(array[i]);
}
}

return getBiggerSize(candidates);
}

/*
* read the pricing extension with this format: <Extension type='StickyPricing'><Price currency="EUR">1.0000</Price></Extension>
* @return {object} pricing data in format: {currency: "EUR", price:"1.000"}
*/
function getPricing(xmlNode) {
var pricingExtNode;
var princingData = {};

var extensions = xmlNode.querySelectorAll('Extension');
// Nodelist.forEach is not supported in IE and Edge
// Workaround given here https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/10638731/
Array.prototype.forEach.call(extensions, function(node) {
if (node.getAttribute('type') === 'StickyPricing') {
pricingExtNode = node;
}
});

if (pricingExtNode) {
var priceNode = pricingExtNode.querySelector('Price');
princingData = {
currency: priceNode.getAttribute('currency'),
price: priceNode.textContent || priceNode.innerText
};
} else {
utils.logWarn('PREBID - ' + BIDDER_CODE + ': Can\'t get pricing data. Is price awareness enabled?');
}

return princingData;
}

function hashcode(inputString) {
var hash = 0;
var char;
if (inputString.length == 0) return hash;
for (var i = 0; i < inputString.length; i++) {
char = inputString.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash; // Convert to 32bit integer
}
return hash;
}

function getCreativeId(xmlNode) {
var creaId = '';
var adNodes = xmlNode.querySelectorAll('Ad');
// Nodelist.forEach is not supported in IE and Edge
// Workaround given here https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/10638731/
Array.prototype.forEach.call(adNodes, function(el) {
creaId += '[' + el.getAttribute('id') + ']';
});

return creaId;
}

/**
* returns the top most accessible window
*/
function getTopMostWindow() {
var res = window;

try {
while (top !== res) {
if (res.parent.location.href.length) { res = res.parent; }
}
} catch (e) {}

return res;
}

function getComponentId(inputFormat) {
var component = 'mustang'; // default component id

if (inputFormat && inputFormat !== 'inbanner') {
// format identifiers are equals to their component ids.
component = inputFormat;
}

return component;
}

function getAPIName(componentId) {
componentId = componentId || '';

// remove dash in componentId to get API name
return componentId.replace('-', '');
}

function formatAdHTML(bid, size) {
var integrationType = bid.params.format;

var divHtml = '<div id="freewheelssp_prebid_target" style="width:' + size[0] + 'px;height:' + size[1] + 'px;"></div>';

var script = '';
var libUrl = '';
if (integrationType && integrationType !== 'inbanner') {
libUrl = PRIMETIME_URL + getComponentId(bid.params.format) + '.min.js';
script = getOutstreamScript(bid, size);
} else {
libUrl = MUSTANG_URL;
script = getInBannerScript(bid, size);
}

return divHtml +
'<script type=\'text/javascript\'>' +
'(function() {' +
' var st = document.createElement(\'script\'); st.type = \'text/javascript\'; st.async = true;' +
' st.src = \'' + libUrl + '\';' +
' st.onload = function(){' +
' var vastLoader = new window.com.stickyadstv.vast.VastLoader();' +
' var vast = vastLoader.getVast();' +
// get the top most accessible window
' var topWindow = (function(){var res=window; try{while(top != res){if(res.parent.location.href.length)res=res.parent;}}catch(e){}return res;})();' +
// inject the xml in the Vast object as string
' vast.setXmlString(topWindow.freewheelssp_cache["' + bid.adUnitCode + '"]);' +
// force ad parsing on the given vast xml
' vastLoader.parseAds(vast, {' +
' onSuccess: function() {' + script + ' }' +
' });' +
' };' +
' document.head.appendChild(st);' +
'})();' +
'</script>';
}

var getInBannerScript = function(bid, size) {
return 'var config = {' +
' preloadedVast:vast,' +
' autoPlay:true' +
' };' +
' var ad = new window.com.stickyadstv.vpaid.Ad(document.getElementById("freewheelssp_prebid_target"),config);' +
' (new window.com.stickyadstv.tools.ASLoader(' + bid.params.zoneId + ', \'' + getComponentId(bid.params.format) + '\')).registerEvents(ad);' +
' ad.initAd(' + size[0] + ',' + size[1] + ',"",0,"","");';
};

var getOutstreamScript = function(bid) {
var config = bid.params;

// default placement if no placement is set
if (!config.hasOwnProperty('domId') && !config.hasOwnProperty('auto') && !config.hasOwnProperty('p') && !config.hasOwnProperty('article')) {
if (config.format === 'intext-roll') {
config.iframeMode = 'dfp';
} else {
config.domId = 'freewheelssp_prebid_target';
}
}

var script = 'var config = {' +
' preloadedVast:vast,' +
' ASLoader:new window.com.stickyadstv.tools.ASLoader(' + bid.params.zoneId + ', \'' + getComponentId(bid.params.format) + '\')';

for (var key in config) {
// dont' send format parameter
// neither zone nor vastUrlParams value as Vast is already loaded
if (config.hasOwnProperty(key) && key !== 'format' && key !== 'zone' && key !== 'zoneId' && key !== 'vastUrlParams') {
script += ',' + key + ':"' + config[key] + '"';
}
}
script += '};' +

'window.com.stickyadstv.' + getAPIName(bid.params.format) + '.start(config);';

return script;
};

export const spec = {
code: BIDDER_CODE,
supportedMediaTypes: ['banner', 'video'],
aliases: ['stickyadstv'], // former name for freewheel-ssp
/**
* Determines whether or not the given bid request is valid.
*
* @param {object} bid The bid to validate.
* @return boolean True if this is a valid bid, and false otherwise.
*/
isBidRequestValid: function(bid) {
return !!(bid.params.zoneId);
},

/**
* Make a server request from the list of BidRequests.
*
* @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, bidderRequest) {
// var currency = config.getConfig(currency);

var currentBidRequest = bidRequests[0];
if (bidRequests.length > 1) {
utils.logMessage('Prebid.JS - freewheel bid adapter: only one ad unit is required.');
}

var zone = currentBidRequest.params.zoneId;
var timeInMillis = new Date().getTime();
var keyCode = hashcode(zone + '' + timeInMillis);
var requestParams = {
reqType: 'AdsSetup',
protocolVersion: '2.0',
zoneId: zone,
componentId: getComponentId(currentBidRequest.params.format),
timestamp: timeInMillis,
pKey: keyCode
};

// Add GDPR flag and consent string
if (bidderRequest && bidderRequest.gdprConsent) {
requestParams._fw_gdpr_consent = bidderRequest.gdprConsent.consentString;

if (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') {
requestParams._fw_gdpr = bidderRequest.gdprConsent.gdprApplies;
}
}

if (currentBidRequest.params.gdpr_consented_providers) {
requestParams._fw_gdpr_consented_providers = currentBidRequest.params.gdpr_consented_providers;
}

var vastParams = currentBidRequest.params.vastUrlParams;
if (typeof vastParams === 'object') {
for (var key in vastParams) {
if (vastParams.hasOwnProperty(key)) {
requestParams[key] = vastParams[key];
}
}
}

var location = (bidderRequest && bidderRequest.refererInfo) ? bidderRequest.refererInfo.referer : getTopMostWindow().location.href;
if (isValidUrl(location)) {
requestParams.loc = location;
}

var playerSize = getBiggerSizeWithLimit(currentBidRequest.mediaTypes.banner.sizes, currentBidRequest.mediaTypes.banner.minSizeLimit, currentBidRequest.mediaTypes.banner.maxSizeLimit);

if (playerSize[0] > 0 || playerSize[1] > 0) {
requestParams.playerSize = playerSize[0] + 'x' + playerSize[1];
}

return {
method: 'GET',
url: FREEWHEEL_ADSSETUP,
data: requestParams,
bidRequest: currentBidRequest
};
},

/**
* Unpack the response from the server into a list of bids.
*
* @param {*} serverResponse A successful response from the server.
* @param {object} request: the built request object containing the initial bidRequest.
* @return {Bid[]} An array of bids which were nested inside the server.
*/
interpretResponse: function(serverResponse, request) {
var bidrequest = request.bidRequest;
var playerSize = getBiggerSizeWithLimit(bidrequest.mediaTypes.banner.sizes, bidrequest.mediaTypes.banner.minSizeLimit, bidrequest.mediaTypes.banner.maxSizeLimit);

if (typeof serverResponse == 'object' && typeof serverResponse.body == 'string') {
serverResponse = serverResponse.body;
}

var xmlDoc;
try {
var parser = new DOMParser();
xmlDoc = parser.parseFromString(serverResponse, 'application/xml');
} catch (err) {
utils.logWarn('Prebid.js - ' + BIDDER_CODE + ' : ' + err);
return;
}

const princingData = getPricing(xmlDoc);
const creativeId = getCreativeId(xmlDoc);

const topWin = getTopMostWindow();
if (!topWin.freewheelssp_cache) {
topWin.freewheelssp_cache = {};
}
topWin.freewheelssp_cache[bidrequest.adUnitCode] = serverResponse;

const bidResponses = [];

if (princingData.price) {
const bidResponse = {
requestId: bidrequest.bidId,
cpm: princingData.price,
width: playerSize[0],
height: playerSize[1],
creativeId: creativeId,
currency: princingData.currency,
netRevenue: true,
ttl: 360
};

var mediaTypes = bidrequest.mediaTypes || {};
if (mediaTypes.video) {
// bidResponse.vastXml = serverResponse;
bidResponse.mediaType = 'video';

var blob = new Blob([serverResponse], {type: 'application/xml'});
bidResponse.vastUrl = window.URL.createObjectURL(blob);
} else {
bidResponse.ad = formatAdHTML(bidrequest, playerSize);
}

bidResponses.push(bidResponse);
}

return bidResponses;
},

getUserSyncs: function(syncOptions) {
if (syncOptions && syncOptions.pixelEnabled) {
return [{
type: 'image',
url: USER_SYNC_URL
}];
} else {
return [];
}
},

}
registerBidder(spec);
12 changes: 9 additions & 3 deletions modules/freewheel-sspBidAdapter.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,21 @@ Module that connects to Freewheel ssp's demand sources
var adUnits = [
{
code: 'test-div',
sizes: [[300, 250]], // a display size
mediaTypes: {
banner: {
sizes: [[300, 250]], // a display size
}
},
bids: [
{
bidder: "freewheel-ssp",
params: {
zoneId : '41852'
zoneId : '277225'
}
}
]
}
];
```
```
Loading

0 comments on commit c015d79

Please sign in to comment.