Skip to content

Commit

Permalink
Add freewheel ssp bidder adapter for prebid 1.0 (#1793)
Browse files Browse the repository at this point in the history
* add stickyadsTV bidder adapter

* init unit test file

* ad some unit tests

* fix unit test on ad format with parameters

* add some unit tests

* add unit tests on getBid method

* add some test cases in unit tests

* minor fix on component id tag.

* remove adapters-sticky.json test file

* use top most accessible window instead of window.top

* Pass in the bid request in the createBid call.

* use top most accessible window instead of window.top

* add unit tests

* update unit tests

* fix unit test.

* fix CI build

* add alias freewheel-ssp

* update unit tests on bidderCode value

* fix component id values and add unit tests

* allws to use any outstream format.

* fix ASLoader on futur outstream format versions

* minor: fix code format.

* update unit tests

* minor fix code format

* minor: add missing new line at eof

* replace StickyAdsTVAdapter by freewheel ssp bd adapter (for prebid 1.0)

* remove old stickyadstv unittest spec.

* fix server response parsing if sent as object with 'body' field

* use the vastXml field for video mediatype

* add user sync pixel in freewheel ssp adapter

* remove all console log calls (replaced using util helper)

* remove useless bidderCode (automatically added by the bidderFactory)

* Return the SYNC pixel to be added in the page by Prebid.js

* remove instance level properties to enable concurrent bids with the same adapter instance.

* fix the request apss through and corresponding unit tests

* fix 'freeheelssp' typo
  • Loading branch information
guillaume-sticky authored and jaiminpanchal27 committed Dec 19, 2017
1 parent a936adb commit 4bb4aaa
Show file tree
Hide file tree
Showing 3 changed files with 534 additions and 0 deletions.
314 changes: 314 additions & 0 deletions modules/freewheelSSPBidAdapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,314 @@
import * as utils from 'src/utils';
import { registerBidder } from 'src/adapters/bidderFactory';
// import { config } from 'src/config';

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() {
if (location.protocol && location.protocol.indexOf('https') === 0) {
return 'https';
} else {
return 'http';
}
}

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;
}

/*
* 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');
extensions.forEach(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 getCreativeId(xmlNode) {
var creaId = '';
var adNodes = xmlNode.querySelectorAll('Ad');

adNodes.forEach(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"></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 placementCode = bid.adUnitCode;

var config = bid.params;

// default placement if no placement is set
if (!config.hasOwnProperty('domId') && !config.hasOwnProperty('auto') && !config.hasOwnProperty('p') && !config.hasOwnProperty('article')) {
config.domId = placementCode;
}

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: ['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) {
// 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 requestParams = {
reqType: 'AdsSetup',
protocolVersion: '2.0',
zoneId: currentBidRequest.params.zoneId,
componentId: getComponentId(currentBidRequest.params.format)
};

var location = utils.getTopWindowUrl();
if (isValidUrl(location)) {
requestParams.loc = location;
}

var playerSize = getBiggerSize(currentBidRequest.sizes);
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 = getBiggerSize(bidrequest.sizes);

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.pixelEnabled) {
return [{
type: 'image',
url: USER_SYNC_URL
}];
}
}
}
registerBidder(spec);
27 changes: 27 additions & 0 deletions modules/freewheelSSPBidAdapter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Overview

Module Name: Freewheel SSP Bidder Adapter
Module Type: Bidder Adapter
Maintainer: clientsidesdk@freewheel.tv

# Description

Module that connects to Freewheel ssp's demand sources

# Test Parameters
```
var adUnits = [
{
code: 'test-div',
sizes: [[300, 250]], // a display size
bids: [
{
bidder: "freewheel-ssp",
params: {
zoneId : '277225'
}
}
]
}
];
```
Loading

0 comments on commit 4bb4aaa

Please sign in to comment.