Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add adapter for IAS #2056

Merged
merged 6 commits into from
Feb 8, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions modules/iasBidAdapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import * as utils from 'src/utils';
import { registerBidder } from 'src/adapters/bidderFactory';

const BIDDER_CODE = 'ias';

function isBidRequestValid(bid) {
const { pubId, adUnitPath } = bid.params;
return !!(pubId && adUnitPath);
}

/**
* Converts GPT-style size array into a string
* @param {Array} sizes: list of GPT-style sizes, e.g. [[300, 250], [300, 300]]
* @return {String} a string containing sizes, e.g. '[300.250,300.300]'
*/
function stringifySlotSizes(sizes) {
let result = '';
if (utils.isArray(sizes)) {
result = sizes.reduce((acc, size) => {
acc.push(size.join('.'));
return acc;
}, []);
result = '[' + result.join(',') + ']';
}
return result;
}

function stringifySlot(bidRequest) {
const id = bidRequest.adUnitCode;
const ss = stringifySlotSizes(bidRequest.sizes);
const p = bidRequest.params.adUnitPath;
const slot = { id, ss, p };
const keyValues = utils.getKeys(slot).map(function(key) {
return [key, slot[key]].join(':');
});
return '{' + keyValues.join(',') + '}';
}

function stringifyWindowSize() {
return [window.innerWidth || -1, window.innerHeight || -1].join('.');
}

function stringifyScreenSize() {
return [(window.screen && window.screen.width) || -1, (window.screen && window.screen.height) || -1].join('.');
}

function buildRequests(bidRequests) {
const IAS_HOST = '//pixel.adsafeprotected.com/services/pub';
const anId = bidRequests[0].params.pubId;

let queries = [];
queries.push(['anId', anId]);
queries = queries.concat(bidRequests.reduce(function(acc, request) {
acc.push(['slot', stringifySlot(request)]);
return acc;
}, []));

queries.push(['wr', stringifyWindowSize()]);
queries.push(['sr', stringifyScreenSize()]);

const queryString = encodeURI(queries.map(qs => qs.join('=')).join('&'));

return {
method: 'GET',
url: IAS_HOST,
data: queryString,
bidRequest: bidRequests[0]
}
}

function getPageLevelKeywords(response) {
let result = {};
shallowMerge(result, response.brandSafety);
result.fr = response.fr;
return result;
}

function shallowMerge(dest, src) {
utils.getKeys(src).reduce((dest, srcKey) => {
dest[srcKey] = src[srcKey];
return dest;
}, dest);
}

function interpretResponse(serverResponse, request) {
const iasResponse = serverResponse.body;
const bidResponses = [];

// Keys in common bid response are not used;
// Necessary to get around with prebid's common bid response check
const commonBidResponse = {
requestId: request.bidRequest.bidId,
cpm: 0.01,
width: 100,
height: 200,
creativeId: 434,
dealId: 42,
currency: 'usd',
netRevenue: true,
ttl: 360
};

shallowMerge(commonBidResponse, getPageLevelKeywords(iasResponse));
commonBidResponse.slots = iasResponse.slots;
bidResponses.push(commonBidResponse);
return bidResponses;
}

export const spec = {
code: BIDDER_CODE,
aliases: [],
isBidRequestValid: isBidRequestValid,
buildRequests: buildRequests,
interpretResponse: interpretResponse
};

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

```
Module Name: Integral Ad Science(IAS) Bidder Adapter
Module Type: Bidder Adapter
Maintainer: kat@integralads.com
```

# Description

This module is an integration with prebid.js with an IAS product, pet.js. It is not a bidder per se but works in a similar way: retrieve data that publishers might be interested in setting keyword targeting.

# Test Parameters
```
var adUnits = [
{
code: 'ias-dfp-test-async',
sizes: [[300, 250]], // a display size
bids: [
{
bidder: "ias",
params: {
pubId: '99',
adUnitPath: '/57514611/news.com'
}
}
]
}
];
```
190 changes: 190 additions & 0 deletions test/spec/modules/iasBidAdapter_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
import { expect } from 'chai';
import { spec } from 'modules/iasBidAdapter';

describe('iasBidAdapter is an adapter that', () => {
it('has the correct bidder code', () => {
expect(spec.code).to.equal('ias');
});
describe('has a method `isBidRequestValid` that', () => {
it('exists', () => {
expect(spec.isBidRequestValid).to.be.a('function');
});
it('returns false if bid params misses `pubId`', () => {
expect(spec.isBidRequestValid(
{
params: {
adUnitPath: 'someAdUnitPath'
}
})).to.equal(false);
});
it('returns false if bid params misses `adUnitPath`', () => {
expect(spec.isBidRequestValid(
{
params: {
pubId: 'somePubId'
}
})).to.equal(false);
});
it('returns true otherwise', () => {
expect(spec.isBidRequestValid(
{
params: {
adUnitPath: 'someAdUnitPath',
pubId: 'somePubId',
someOtherParam: 'abc'
}
})).to.equal(true);
});
});

describe('has a method `buildRequests` that', () => {
it('exists', () => {
expect(spec.buildRequests).to.be.a('function');
});
describe('given bid requests, returns a `ServerRequest` instance that', () => {
let bidRequests, IAS_HOST;
beforeEach(() => {
IAS_HOST = '//pixel.adsafeprotected.com/services/pub';
bidRequests = [
{
adUnitCode: 'one-div-id',
auctionId: 'someAuctionId',
bidId: 'someBidId',
bidder: 'ias',
bidderRequestId: 'someBidderRequestId',
params: {
pubId: '1234',
adUnitPath: '/a/b/c'
},
sizes: [
[10, 20],
[300, 400]
],
transactionId: 'someTransactionId'
},
{
adUnitCode: 'two-div-id',
auctionId: 'someAuctionId',
bidId: 'someBidId',
bidder: 'ias',
bidderRequestId: 'someBidderRequestId',
params: {
pubId: '1234',
adUnitPath: '/d/e/f'
},
sizes: [
[50, 60]
],
transactionId: 'someTransactionId'
}
];
});
it('has property `method` of `GET`', () => {
expect(spec.buildRequests(bidRequests)).to.deep.include({
method: 'GET'
});
});
it('has property `url` to be the correct IAS endpoint', () => {
expect(spec.buildRequests(bidRequests)).to.deep.include({
url: IAS_HOST
});
});
describe('has property `data` that is an encode query string containing information such as', () => {
let val;
const ANID_PARAM = 'anId';
const SLOT_PARAM = 'slot';
const SLOT_ID_PARAM = 'id';
const SLOT_SIZE_PARAM = 'ss';
const SLOT_AD_UNIT_PATH_PARAM = 'p';

beforeEach(() => val = decodeURI(spec.buildRequests(bidRequests).data));
it('publisher id', () => {
expect(val).to.have.string(`${ANID_PARAM}=1234`);
});
it('ad slot`s id, size and ad unit path', () => {
expect(val).to.have.string(`${SLOT_PARAM}={${SLOT_ID_PARAM}:one-div-id,${SLOT_SIZE_PARAM}:[10.20,300.400],${SLOT_AD_UNIT_PATH_PARAM}:/a/b/c}`);
expect(val).to.have.string(`${SLOT_PARAM}={${SLOT_ID_PARAM}:two-div-id,${SLOT_SIZE_PARAM}:[50.60],${SLOT_AD_UNIT_PATH_PARAM}:/d/e/f}`);
});
it('window size', () => {
expect(val).to.match(/.*wr=[0-9]*\.[0-9]*/);
});
it('screen size', () => {
expect(val).to.match(/.*sr=[0-9]*\.[0-9]*/);
});
});
it('has property `bidRequest` that is the first passed in bid request', () => {
expect(spec.buildRequests(bidRequests)).to.deep.include({
bidRequest: bidRequests[0]
});
});
});
});
describe('has a method `interpretResponse` that', () => {
it('exists', () => {
expect(spec.interpretResponse).to.be.a('function');
});
describe('returns a list of bid response that', () => {
let bidResponse, slots;
beforeEach(() => {
const request = {
bidRequest: {
bidId: '102938'
}
};
slots = {};
slots['test-div-id'] = {
id: '1234',
vw: ['60', '70']
};
slots['test-div-id-two'] = {
id: '5678',
vw: ['80', '90']
};
const serverResponse = {
body: {
brandSafety: {
adt: 'adtVal',
alc: 'alcVal',
dlm: 'dlmVal',
drg: 'drgVal',
hat: 'hatVal',
off: 'offVal',
vio: 'vioVal'
},
fr: 'false',
slots: slots
},
headers: {}
};
bidResponse = spec.interpretResponse(serverResponse, request);
});
it('has IAS keyword `adt` as property', () => {
expect(bidResponse[0]).to.deep.include({ adt: 'adtVal' });
});
it('has IAS keyword `alc` as property', () => {
expect(bidResponse[0]).to.deep.include({ alc: 'alcVal' });
});
it('has IAS keyword `dlm` as property', () => {
expect(bidResponse[0]).to.deep.include({ dlm: 'dlmVal' });
});
it('has IAS keyword `drg` as property', () => {
expect(bidResponse[0]).to.deep.include({ drg: 'drgVal' });
});
it('has IAS keyword `hat` as property', () => {
expect(bidResponse[0]).to.deep.include({ hat: 'hatVal' });
});
it('has IAS keyword `off` as property', () => {
expect(bidResponse[0]).to.deep.include({ off: 'offVal' });
});
it('has IAS keyword `vio` as property', () => {
expect(bidResponse[0]).to.deep.include({ vio: 'vioVal' });
});
it('has IAS keyword `fr` as property', () => {
expect(bidResponse[0]).to.deep.include({ fr: 'false' });
});
it('has property `slots`', () => {
expect(bidResponse[0]).to.deep.include({ slots: slots });
});
});
});
});