Skip to content

Commit

Permalink
New adapter: Adhese (prebid#3384)
Browse files Browse the repository at this point in the history
* Add Adhese adapter

* Support empty impressionCounters

* Fix the description

* Replace Object.entries with Object.keys

* Cleanup

* Use const instead of let

* Internal functions are no longer exposed

* Rename getter

* Use the bid parameters pointing to the existing demo position
  • Loading branch information
mefjush authored and Pedro López Jiménez committed Mar 18, 2019
1 parent 0e06301 commit 617e4c3
Show file tree
Hide file tree
Showing 3 changed files with 491 additions and 0 deletions.
166 changes: 166 additions & 0 deletions modules/adheseBidAdapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
'use strict';

import { registerBidder } from 'src/adapters/bidderFactory';
import { BANNER, VIDEO } from 'src/mediaTypes';

const BIDDER_CODE = 'adhese';
const USER_SYNC_BASE_URL = 'https://user-sync.adhese.com/iframe/user_sync.html';

export const spec = {
code: BIDDER_CODE,
supportedMediaTypes: [BANNER, VIDEO],

isBidRequestValid: function(bid) {
return !!(bid.params.account && bid.params.location && bid.params.format);
},

buildRequests: function(validBidRequests, bidderRequest) {
if (validBidRequests.length === 0) {
return null;
}

const account = getAccount(validBidRequests);
const targets = validBidRequests.map(bid => bid.params.data).reduce(mergeTargets, {});
const gdprParams = (bidderRequest.gdprConsent && bidderRequest.gdprConsent.consentString) ? [ 'xt' + bidderRequest.gdprConsent.consentString ] : [];
const targetsParams = Object.keys(targets).map(targetCode => targetCode + targets[targetCode].join(';'));
const slotsParams = validBidRequests.map(bid => 'sl' + bidToSlotName(bid));
const params = [...slotsParams, ...targetsParams, ...gdprParams].map(s => '/' + s).join('');
const cacheBuster = '?t=' + new Date().getTime();
const uri = 'https://ads-' + account + '.adhese.com/json' + params + cacheBuster;

return {
method: 'GET',
url: uri,
bids: validBidRequests
};
},

interpretResponse: function(serverResponse, request) {
const serverAds = serverResponse.body.reduce(function(map, ad) {
map[ad.slotName] = ad;
return map;
}, {});

serverResponse.account = getAccount(request.bids);

return request.bids
.map(bid => ({
bid: bid,
ad: serverAds[bidToSlotName(bid)]
}))
.filter(item => item.ad)
.map(item => adResponse(item.bid, item.ad));
},

getUserSyncs: function(syncOptions, serverResponse, gdprConsent) {
const account = serverResponse.account || '';
if (syncOptions.iframeEnabled) {
let syncurl = USER_SYNC_BASE_URL + '?account=' + account;
if (gdprConsent) {
syncurl += '&gdpr=' + (gdprConsent.gdprApplies ? 1 : 0);
syncurl += '&consentString=' + encodeURIComponent(gdprConsent.consentString || '');
}
return [{ type: 'iframe', url: syncurl }];
}
}
};

function adResponse(bid, ad) {
const price = getPrice(ad);
const adDetails = getAdDetails(ad);
const markup = getAdMarkup(ad);

const bidResponse = getbaseAdResponse({
requestId: bid.bidId,
mediaType: getMediaType(markup),
cpm: Number(price.amount),
currency: price.currency,
width: Number(ad.width),
height: Number(ad.height),
creativeId: adDetails.creativeId,
dealId: adDetails.dealId
});

if (bidResponse.mediaType === VIDEO) {
bidResponse.vastXml = markup;
} else {
const counter = ad.impressionCounter ? "<img src='" + ad.impressionCounter + "' style='height:1px; width:1px; margin: -1px -1px; display:none;'/>" : '';
bidResponse.ad = markup + counter;
}
return bidResponse;
}

function mergeTargets(targets, target) {
if (target) {
Object.keys(target).forEach(function (key) {
const val = target[key];
const values = Array.isArray(val) ? val : [val];
if (targets[key]) {
const distinctValues = values.filter(v => targets[key].indexOf(v) < 0);
targets[key].push.apply(targets[key], distinctValues);
} else {
targets[key] = values;
}
});
}
return targets;
}

function bidToSlotName(bid) {
return bid.params.location + '-' + bid.params.format;
}

function getAccount(validBidRequests) {
return validBidRequests[0].params.account;
}

function getbaseAdResponse(response) {
return Object.assign({ netRevenue: true, ttl: 360 }, response);
}

function isAdheseAd(ad) {
return !ad.origin || ad.origin === 'JERLICIA';
}

function getMediaType(markup) {
const isVideo = markup.trim().toLowerCase().match(/<\?xml|<vast/);
return isVideo ? VIDEO : BANNER;
}

function getAdMarkup(ad) {
if (!isAdheseAd(ad) || (ad.ext === 'js' && ad.body !== undefined && ad.body !== '' && ad.body.match(/<script|<SCRIPT|<html|<HTML|<\?xml/))) {
return ad.body
} else {
return ad.tag;
}
}

function getPrice(ad) {
if (ad.extension && ad.extension.prebid && ad.extension.prebid.cpm) {
return ad.extension.prebid.cpm;
}
return { amount: 0, currency: 'USD' };
}

function getAdDetails(ad) {
let creativeId = '';
let dealId = '';

if (isAdheseAd(ad)) {
creativeId = ad.id;
dealId = ad.orderId;
} else {
creativeId = ad.origin + (ad.originInstance ? '-' + ad.originInstance : '');
if (ad.originData && ad.originData.seatbid && ad.originData.seatbid.length) {
const seatbid = ad.originData.seatbid[0];
if (seatbid.bid && seatbid.bid.length) {
const bid = seatbid.bid[0];
creativeId = String(bid.crid || '');
dealId = String(bid.dealid || '');
}
}
}
return { creativeId: creativeId, dealId: dealId };
}

registerBidder(spec);
40 changes: 40 additions & 0 deletions modules/adheseBidAdapter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Overview

```
Module Name: Adhese Bidder Adapter
Module Type: Bidder Adapter
Maintainer: info@adhese.com
```

# Description

Module that connects with Adhese Adserver and Adhese Gateway. Banner and Video are supported.

# Test Parameters
```
var adUnits = [
{
code: 'test-div1',
mediaTypes: {
banner: {
sizes: [[728, 90], [850, 150]], // a display size
}
},
bids: [
{
bidder: "adhese",
params: {
account: 'demo', // required - the name of your adhese account, if unknown, please contact your sales rep
location: '_adhese_prebid_demo_', // required - the location you want to refer to for a specific section or page, as defined in your Adhese inventory
format: 'leaderboard', // required - the format you accept for this unit, as defined in your Adhese inventory
data: { // optional - target params, as defined in your Adhese setup
'ci': ['gent', 'brussels']
'ag': ['55']
'tl': ['all']
}
}
}
]
}
];
```
Loading

0 comments on commit 617e4c3

Please sign in to comment.