Skip to content

Commit

Permalink
Project Limelight bidder adapter (#3835)
Browse files Browse the repository at this point in the history
* Add Project Limelight adapter

* Change to relative paths
  • Loading branch information
apykhteyev authored and jsnellbaker committed May 20, 2019
1 parent 034b57e commit 45b519a
Show file tree
Hide file tree
Showing 3 changed files with 350 additions and 0 deletions.
125 changes: 125 additions & 0 deletions modules/projectLimeLightBidAdapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { registerBidder } from '../src/adapters/bidderFactory';
import { BANNER, VIDEO } from '../src/mediaTypes';
import {ajax} from '../src/ajax';
import * as utils from '../src/utils';

const BIDDER_CODE = 'project-limelight';
const URL = '//ads.project-limelight.com/hb';

/**
* Determines whether or not the given bid response is valid.
*
* @param {object} bid The bid to validate.
* @return boolean True if this is a valid bid, and false otherwise.
*/
function isBidResponseValid(bid) {
if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) {
return false;
}
switch (bid.mediaType) {
case BANNER:
return Boolean(bid.width && bid.height && bid.ad);
case VIDEO:
return Boolean(bid.vastXml || bid.vastUrl);
}
return false;
}

function extractBidSizes(bid) {
const bidSizes = [];

bid.sizes.forEach(size => {
bidSizes.push({
width: size[0],
height: size[1]
});
});

return bidSizes;
}

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

/**
* 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: (bid) => {
return Boolean(bid.bidId && bid.params);
},

/**
* Make a server request from the list of BidRequests.
*
* @return ServerRequest Info describing the request to the server.
*/
buildRequests: (validBidRequests, bidderRequest) => {
let winTop;
try {
winTop = window.top;
winTop.location.toString();
} catch (e) {
utils.logMessage(e);
winTop = window;
};
const placements = [];
const request = {
'secure': (location.protocol === 'https:'),
'deviceWidth': winTop.screen.width,
'deviceHeight': winTop.screen.height,
'adUnits': placements
};
for (let i = 0; i < validBidRequests.length; i++) {
const bid = validBidRequests[i];
const params = bid.params;
placements.push({
id: params.adUnitId,
bidId: bid.bidId,
transactionId: bid.transactionId,
sizes: extractBidSizes(bid),
type: params.adUnitType.toUpperCase()
});
}
return {
method: 'POST',
url: URL,
data: request
};
},

onBidWon: (bid) => {
const cpm = bid.pbMg;
if (bid.nurl !== '') {
bid.nurl = bid.nurl.replace(
/\$\{AUCTION_PRICE\}/,
cpm
);
ajax(bid.nurl, null);
};
},

/**
* Unpack the response from the server into a list of bids.
*
* @param {*} serverResponse A successful response from the server.
* @return {Bid[]} An array of bids which were nested inside the server.
*/
interpretResponse: (bidResponses) => {
const res = [];
const bidResponsesBody = bidResponses.body;
const len = bidResponsesBody.length;
for (let i = 0; i < len; i++) {
const bid = bidResponsesBody[i];
if (isBidResponseValid(bid)) {
res.push(bid);
}
}
return res;
},
};

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

```
Module Name: Project LimeLight SSP Adapter
Module Type: Bidder Adapter
Maintainer: [email protected]
```

# Description

Module that connects to Project Limelight SSP demand sources

# Test Parameters for banner
```
var adUnits = [{
code: 'placementCode',
sizes: [[300, 250]],
bids: [{
bidder: 'project-limelight',
params: {
adUnitId: 0,
adUnitType: 'banner'
}
}]
}
];
```

# Test Parameters for video
```
var videoAdUnit = [{
code: 'video1',
sizes: [[300, 250]],
bids: [{
bidder: 'project-limelight',
params: {
adUnitId: 0,
adUnitType: 'video'
}
}]
}];
```

# Configuration

The Project Limelight Bid Adapter expects Prebid Cache(for video) to be enabled so that we can store and retrieve a single vastXml.

```
pbjs.setConfig({
usePrebidCache: true,
cache: {
url: 'https://prebid.adnxs.com/pbc/v1/cache'
}
});
```
170 changes: 170 additions & 0 deletions test/spec/modules/projectLimeLightBidAdapter_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import {expect} from 'chai';
import {spec} from '../../../modules/projectLimeLightBidAdapter';

describe('ProjectLimeLightAdapter', function () {
let bid = {
bidId: '2dd581a2b6281d',
bidder: 'project-limelight',
bidderRequestId: '145e1d6a7837c9',
params: {
adUnitId: 123,
adUnitType: 'banner'
},
placementCode: 'placement_0',
auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d2',
sizes: [[300, 250]],
transactionId: '3bb2f6da-87a6-4029-aeb0-bfe951372e62'
};

describe('buildRequests', function () {
let serverRequest = spec.buildRequests([bid]);
it('Creates a ServerRequest object with method, URL and data', function () {
expect(serverRequest).to.exist;
expect(serverRequest.method).to.exist;
expect(serverRequest.url).to.exist;
expect(serverRequest.data).to.exist;
});
it('Returns POST method', function () {
expect(serverRequest.method).to.equal('POST');
});
it('Returns valid URL', function () {
expect(serverRequest.url).to.equal('//ads.project-limelight.com/hb');
});
it('Returns valid data if array of bids is valid', function () {
let data = serverRequest.data;
expect(data).to.be.an('object');
expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'secure', 'adUnits');
expect(data.deviceWidth).to.be.a('number');
expect(data.deviceHeight).to.be.a('number');
expect(data.secure).to.be.a('boolean');
let adUnits = data['adUnits'];
for (let i = 0; i < adUnits.length; i++) {
let adUnit = adUnits[i];
expect(adUnit).to.have.all.keys('id', 'bidId', 'type', 'sizes', 'transactionId');
expect(adUnit.id).to.be.a('number');
expect(adUnit.bidId).to.be.a('string');
expect(adUnit.type).to.be.a('string');
expect(adUnit.transactionId).to.be.a('string');
expect(adUnit.sizes).to.be.an('array');
}
});
it('Returns empty data if no valid requests are passed', function () {
serverRequest = spec.buildRequests([]);
let data = serverRequest.data;
expect(data.adUnits).to.be.an('array').that.is.empty;
});
});
describe('interpretBannerResponse', function () {
let resObject = {
body: [ {
requestId: '123',
mediaType: 'banner',
cpm: 0.3,
width: 320,
height: 50,
ad: '<h1>Hello ad</h1>',
ttl: 1000,
creativeId: '123asd',
netRevenue: true,
currency: 'USD'
} ]
};
let serverResponses = spec.interpretResponse(resObject);
it('Returns an array of valid server responses if response object is valid', function () {
expect(serverResponses).to.be.an('array').that.is.not.empty;
for (let i = 0; i < serverResponses.length; i++) {
let dataItem = serverResponses[i];
expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId',
'netRevenue', 'currency', 'mediaType');
expect(dataItem.requestId).to.be.a('string');
expect(dataItem.cpm).to.be.a('number');
expect(dataItem.width).to.be.a('number');
expect(dataItem.height).to.be.a('number');
expect(dataItem.ad).to.be.a('string');
expect(dataItem.ttl).to.be.a('number');
expect(dataItem.creativeId).to.be.a('string');
expect(dataItem.netRevenue).to.be.a('boolean');
expect(dataItem.currency).to.be.a('string');
expect(dataItem.mediaType).to.be.a('string');
}
it('Returns an empty array if invalid response is passed', function () {
serverResponses = spec.interpretResponse('invalid_response');
expect(serverResponses).to.be.an('array').that.is.empty;
});
});
});
describe('interpretVideoResponse', function () {
let resObject = {
body: [ {
requestId: '123',
mediaType: 'video',
cpm: 0.3,
width: 320,
height: 50,
vastXml: '<VAST></VAST>',
ttl: 1000,
creativeId: '123asd',
netRevenue: true,
currency: 'USD'
} ]
};
let serverResponses = spec.interpretResponse(resObject);
it('Returns an array of valid server responses if response object is valid', function () {
expect(serverResponses).to.be.an('array').that.is.not.empty;
for (let i = 0; i < serverResponses.length; i++) {
let dataItem = serverResponses[i];
expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'vastXml', 'ttl', 'creativeId',
'netRevenue', 'currency', 'mediaType');
expect(dataItem.requestId).to.be.a('string');
expect(dataItem.cpm).to.be.a('number');
expect(dataItem.width).to.be.a('number');
expect(dataItem.height).to.be.a('number');
expect(dataItem.vastXml).to.be.a('string');
expect(dataItem.ttl).to.be.a('number');
expect(dataItem.creativeId).to.be.a('string');
expect(dataItem.netRevenue).to.be.a('boolean');
expect(dataItem.currency).to.be.a('string');
expect(dataItem.mediaType).to.be.a('string');
}
it('Returns an empty array if invalid response is passed', function () {
serverResponses = spec.interpretResponse('invalid_response');
expect(serverResponses).to.be.an('array').that.is.empty;
});
});
});
describe('isBidRequestValid', function() {
let bid = {
bidId: '2dd581a2b6281d',
bidder: 'project-limelight',
bidderRequestId: '145e1d6a7837c9',
params: {
adUnitId: 123,
adUnitType: 'banner'
},
placementCode: 'placement_0',
auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d2',
sizes: [[300, 250]],
transactionId: '3bb2f6da-87a6-4029-aeb0-bfe951372e62'
};

it('should return true when required params found', function() {
expect(spec.isBidRequestValid(bid)).to.equal(true);
});

it('should return false when required params are not passed', function() {
let bidFailed = {
bidder: 'project-limelight',
bidderRequestId: '145e1d6a7837c9',
params: {
adUnitId: 123,
adUnitType: 'banner'
},
placementCode: 'placement_0',
auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d2',
sizes: [[300, 250]],
transactionId: '3bb2f6da-87a6-4029-aeb0-bfe951372e62'
};
expect(spec.isBidRequestValid(bidFailed)).to.equal(false);
});
});
});

0 comments on commit 45b519a

Please sign in to comment.