Skip to content

Commit

Permalink
PulsePoint Lite adapter changes (prebid#1338)
Browse files Browse the repository at this point in the history
* ET-1691: Pulsepoint Analytics adapter for Prebid. (#1)

* ET-1691: Adding pulsepoint analytics and tests for pulsepoint adapter

* ET-1691: Adding pulsepoint analytics and tests for pulsepoint adapter

* ET-1691: cleanup

* ET-1691: minor

* ET-1691: revert package.json change

* Adding bidRequest to bidFactory.createBid method as per prebid#509

* ET-1765: Adding support for additional params in PulsePoint adapter (#2)

* ET-1850: Fixing prebid#866

* Minor fix

* Adding multi imp ORTB support

* replacing existing lite adapter code

* interim commit

* pulseLite - native tests

* pulseLite - native tests

* registering new adapter name via alias
  • Loading branch information
anand-venkatraman authored and matthewlane committed Jul 13, 2017
1 parent 1f0c570 commit fe00cc9
Show file tree
Hide file tree
Showing 2 changed files with 404 additions and 71 deletions.
304 changes: 257 additions & 47 deletions modules/pulsepointLiteBidAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,45 +5,203 @@ import {ajax} from 'src/ajax';
import {STATUS} from 'src/constants';
import adaptermanager from 'src/adaptermanager';

/**
* PulsePoint "Lite" Adapter. This adapter implementation is lighter than the
* alternative/original PulsePointAdapter because it has no external
* dependencies and relies on a single OpenRTB request to the PulsePoint
* bidder instead of separate requests per slot.
*/
function PulsePointLiteAdapter() {
const bidUrl = window.location.protocol + '//bid.contextweb.com/header/tag?';
const bidUrl = window.location.protocol + '//bid.contextweb.com/header/ortb';
const ajaxOptions = {
method: 'GET',
method: 'POST',
withCredentials: true,
contentType: 'text/plain'
};
const NATIVE_DEFAULTS = {
TITLE_LEN: 100,
DESCR_LEN: 200,
SPONSORED_BY_LEN: 50,
IMG_MIN: 150,
ICON_MIN: 50,
};

/**
* Makes the call to PulsePoint endpoint and registers bids.
*/
function _callBids(bidRequest) {
try {
// construct the openrtb bid request from slots
const request = {
imp: bidRequest.bids.map(slot => impression(slot)),
site: site(bidRequest),
device: device(),
};
ajax(bidUrl, (rawResponse) => {
bidResponseAvailable(bidRequest, rawResponse);
}, JSON.stringify(request), ajaxOptions);
} catch (e) {
// register passback on any exceptions while attempting to fetch response.
logError('pulsepoint.requestBid', 'ERROR', e);
bidResponseAvailable(bidRequest);
}
}

function _callBids(bidderRequest) {
bidderRequest.bids.forEach(bidRequest => {
try {
var params = Object.assign({}, environment(), bidRequest.params);
var url = bidUrl + Object.keys(params).map(k => k + '=' + encodeURIComponent(params[k])).join('&');
ajax(url, (bidResponse) => {
bidResponseAvailable(bidRequest, bidResponse);
}, null, ajaxOptions);
} catch (e) {
// register passback on any exceptions while attempting to fetch response.
logError('pulsepoint.requestBid', 'ERROR', e);
bidResponseAvailable(bidRequest);
/**
* Callback for bids, after the call to PulsePoint completes.
*/
function bidResponseAvailable(bidRequest, rawResponse) {
const idToSlotMap = {};
const idToBidMap = {};
// extract the request bids and the response bids, keyed by impr-id
bidRequest.bids.forEach((slot) => {
idToSlotMap[slot.bidId] = slot;
});
const bidResponse = parse(rawResponse);
if (bidResponse) {
bidResponse.seatbid.forEach(seatBid => seatBid.bid.forEach((bid) => {
idToBidMap[bid.impid] = bid;
}));
}
// register the responses
Object.keys(idToSlotMap).forEach((id) => {
if (idToBidMap[id]) {
const size = adSize(idToSlotMap[id]);
const bid = createBid(STATUS.GOOD, bidRequest);
bid.bidderCode = bidRequest.bidderCode;
bid.cpm = idToBidMap[id].price;
bid.adId = id;
if(isNative(idToSlotMap[id])) {
bid.native = nativeResponse(idToSlotMap[id], idToBidMap[id]);
bid.mediaType = 'native';
} else {
bid.ad = idToBidMap[id].adm;
bid.width = size[0];
bid.height = size[1];
}
addBidResponse(idToSlotMap[id].placementCode, bid);
} else {
const passback = createBid(STATUS.NO_BID, bidRequest);
passback.bidderCode = bidRequest.bidderCode;
passback.adId = id;
addBidResponse(idToSlotMap[id].placementCode, passback);
}
});
}

function environment() {
/**
* Produces an OpenRTBImpression from a slot config.
*/
function impression(slot) {
return {
id: slot.bidId,
banner: banner(slot),
native: native(slot),
tagid: slot.params.ct.toString(),
};
}

/**
* Produces an OpenRTB Banner object for the slot given.
*/
function banner(slot) {
const size = adSize(slot);
return slot.nativeParams ? null : {
w: size[0],
h: size[1],
};
}

/**
* Produces an OpenRTB Native object for the slot given.
*/
function native(slot) {
if (slot.nativeParams) {
const assets = [];
addAsset(assets, titleAsset(assets.length + 1, slot.nativeParams.title, NATIVE_DEFAULTS.TITLE_LEN));
addAsset(assets, dataAsset(assets.length + 1, slot.nativeParams.body, 2, NATIVE_DEFAULTS.DESCR_LEN));
addAsset(assets, dataAsset(assets.length + 1, slot.nativeParams.sponsoredBy, 1, NATIVE_DEFAULTS.SPONSORED_BY_LEN));
addAsset(assets, imageAsset(assets.length + 1, slot.nativeParams.icon, 1, NATIVE_DEFAULTS.ICON_MIN, NATIVE_DEFAULTS.ICON_MIN));
addAsset(assets, imageAsset(assets.length + 1, slot.nativeParams.image, 3, NATIVE_DEFAULTS.IMG_MIN, NATIVE_DEFAULTS.IMG_MIN));
return {
request: JSON.stringify({ assets }),
ver: '1.1',
};
}
return null;
}

/**
* Helper method to add an asset to the assets list.
*/
function addAsset(assets, asset) {
if(asset) {
assets.push(asset);
}
}

/**
* Produces a Native Title asset for the configuration given.
*/
function titleAsset(id, params, defaultLen) {
if (params) {
return {
id: id,
required: params.required ? 1 : 0,
title: {
len: params.len || defaultLen,
},
};
}
return null;
}

/**
* Produces a Native Image asset for the configuration given.
*/
function imageAsset(id, params, type, defaultMinWidth, defaultMinHeight) {
return params ? {
id: id,
required: params.required ? 1 : 0,
img: {
type,
wmin: params.wmin || defaultMinWidth,
hmin: params.hmin || defaultMinHeight,
}
} : null;
}

/**
* Produces a Native Data asset for the configuration given.
*/
function dataAsset(id, params, type, defaultLen) {
return params ? {
id: id,
required: params.required ? 1 : 0,
data: {
type,
len: params.len || defaultLen,
}
} : null;
}

/**
* Produces an OpenRTB site object.
*/
function site(bidderRequest) {
const pubId = bidderRequest.bids.length > 0 ? bidderRequest.bids[0].params.cp : '0';
return {
cn: 1,
ca: 'BID',
tl: 1,
'if': 0,
cwu: getTopWindowLocation().href,
cwr: referrer(),
dw: document.documentElement.clientWidth,
cxy: document.documentElement.clientWidth + ',' + document.documentElement.clientHeight,
tz: new Date().getTimezoneOffset(),
ln: (navigator.language || navigator.browserLanguage || navigator.userLanguage || navigator.systemLanguage)
publisher: {
id: pubId.toString(),
},
ref: referrer(),
page: getTopWindowLocation().href,
};
}

/**
* Attempts to capture the referrer url.
*/
function referrer() {
try {
return window.top.document.referrer;
Expand All @@ -52,40 +210,92 @@ function PulsePointLiteAdapter() {
}
}

function bidResponseAvailable(bidRequest, rawResponse) {
if (rawResponse) {
var bidResponse = parse(rawResponse);
if (bidResponse) {
var adSize = bidRequest.params.cf.toUpperCase().split('X');
var bid = createBid(STATUS.GOOD, bidRequest);
bid.bidderCode = bidRequest.bidder;
bid.cpm = bidResponse.bidCpm;
bid.ad = bidResponse.html;
bid.width = adSize[0];
bid.height = adSize[1];
addBidResponse(bidRequest.placementCode, bid);
return;
}
}
var passback = createBid(STATUS.NO_BID, bidRequest);
passback.bidderCode = bidRequest.bidder;
addBidResponse(bidRequest.placementCode, passback);
/**
* Produces an OpenRTB Device object.
*/
function device() {
return {
ua: navigator.userAgent,
language: (navigator.language || navigator.browserLanguage || navigator.userLanguage || navigator.systemLanguage),
};
}

/**
* Safely parses the input given. Returns null on
* parsing failure.
*/
function parse(rawResponse) {
try {
return JSON.parse(rawResponse);
if(rawResponse) {
return JSON.parse(rawResponse);
}
} catch (ex) {
logError('pulsepoint.safeParse', 'ERROR', ex);
return null;
logError('pulsepointLite.safeParse', 'ERROR', ex);
}
return null;
}

/**
* Determines the AdSize for the slot.
*/
function adSize(slot) {
if(slot.params.cf) {
const size = slot.params.cf.toUpperCase().split('X');
const width = parseInt(slot.params.cw || size[0], 10);
const height = parseInt(slot.params.ch || size[1], 10);
return [width, height];
}
return [1, 1];
}

/**
* Parses the native response from the Bid given.
*/
function nativeResponse(slot, bid) {
if(slot.nativeParams) {
const nativeAd = parse(bid.adm);
const keys = {};
if(nativeAd && nativeAd.native && nativeAd.native.assets) {
nativeAd.native.assets.forEach((asset) => {
keys.title = asset.title ? asset.title.text : keys.title;
keys.body = asset.data && asset.data.type === 2 ? asset.data.value : keys.body;
keys.sponsoredBy = asset.data && asset.data.type === 1 ? asset.data.value : keys.sponsoredBy;
keys.image = asset.img && asset.img.type === 3 ? asset.img.url : keys.image;
keys.icon = asset.img && asset.img.type === 1 ? asset.img.url : keys.icon;
});
if (nativeAd.native.link) {
keys.clickUrl = encodeURIComponent(nativeAd.native.link.url);
}
keys.impressionTrackers = nativeAd.native.imptrackers;
return keys;
}
}
return null;
}

/**
* Parses the native response from the Bid given.
*/
function isNative(slot) {
return slot.nativeParams ? true : false;
}

return {
callBids: _callBids
};
}

adaptermanager.registerBidAdapter(new PulsePointLiteAdapter, 'pulsepointLite');
/**
* "pulseLite" will be the adapter name going forward. "pulsepointLite" to be
* deprecated, but kept here for backwards compatibility.
* Reason is key truncation. When the Publisher opts for sending all bids to DFP, then
* the keys get truncated due to the limit in key-size (20 characters, detailed
* here https://support.google.com/dfp_premium/answer/1628457?hl=en). Here is an
* example, where keys got truncated when using the "pulsepointLite" alias - "hb_adid_pulsepointLi=1300bd87d59c4c2"
*/
adaptermanager.registerBidAdapter(new PulsePointLiteAdapter, 'pulseLite', {
supportedMediaTypes: [ 'native' ]
});
adaptermanager.aliasBidAdapter('pulseLite', 'pulsepointLite');

module.exports = PulsePointLiteAdapter;
Loading

0 comments on commit fe00cc9

Please sign in to comment.