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 synacormedia bidder adapter. #3425

Merged
merged 1 commit into from
Jan 11, 2019
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
121 changes: 121 additions & 0 deletions modules/synacormediaBidAdapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
'use strict';

import { getAdUnitSizes, logWarn } from 'src/utils';
import { registerBidder } from 'src/adapters/bidderFactory';
import { BANNER } from 'src/mediaTypes';
import { REPO_AND_VERSION } from 'src/constants';

const SYNACOR_URL = '//prebid.technoratimedia.com';
export const spec = {
code: 'synacormedia',
supportedMediaTypes: [ BANNER ],
sizeMap: {},

isBidRequestValid: function(bid) {
return !!(bid && bid.params && bid.params.placementId && bid.params.seatId);
},
buildRequests: function(validBidReqs, bidderRequest) {
if (!validBidReqs || !validBidReqs.length || !bidderRequest) {
return;
}
let refererInfo = bidderRequest.refererInfo;
let openRtbBidRequest = {
id: bidderRequest.auctionId,
site: {
domain: location.hostname,
page: refererInfo.referer,
ref: document.referrer
},
device: {
ua: navigator.userAgent
},
imp: []
};
let seatId = null;
validBidReqs.forEach((bid, i) => {
if (seatId && seatId !== bid.params.seatId) {
logWarn(`Synacormedia: there is an inconsistent seatId: ${bid.params.seatId} but only sending bid requests for ${seatId}, you should double check your configuration`);
return;
} else {
seatId = bid.params.seatId;
}
let placementId = bid.params.placementId;
let size = getAdUnitSizes(bid)[0];
this.sizeMap[bid.bidId] = size;
openRtbBidRequest.imp.push({
id: bid.bidId,
tagid: placementId,
banner: {
w: size[0],
h: size[1],
pos: 0
}
});
});
if (openRtbBidRequest.imp.length && seatId) {
return {
method: 'POST',
url: `${SYNACOR_URL}/openrtb/bids/${seatId}?src=${REPO_AND_VERSION}`,
data: openRtbBidRequest,
options: {
contentType: 'application/json',
withCredentials: true
}
};
}
},
interpretResponse: function(serverResponse) {
if (!serverResponse.body || typeof serverResponse.body != 'object') {
logWarn('Synacormedia: server returned empty/non-json response: ' + JSON.stringify(serverResponse.body));
return;
}
const {id, seatbid: seatbids} = serverResponse.body;
let bids = [];
if (id && seatbids) {
seatbids.forEach(seatbid => {
seatbid.bid.forEach(bid => {
let size = this.sizeMap[bid.impid] || [0, 0];
let price = parseFloat(bid.price);
let creative = bid.adm.replace(/\${([^}]*)}/g, (match, key) => {
switch (key) {
case 'AUCTION_SEAT_ID': return seatbid.seat;
case 'AUCTION_ID': return id;
case 'AUCTION_BID_ID': return bid.id;
case 'AUCTION_IMP_ID': return bid.impid;
case 'AUCTION_PRICE': return price;
case 'AUCTION_CURRENCY': return 'USD';
}
return match;
});
bids.push({
requestId: bid.impid,
cpm: price,
width: size[0],
height: size[1],
creativeId: seatbid.seat + '~' + bid.crid,
currency: 'USD',
netRevenue: true,
mediaType: BANNER,
ad: creative,
ttl: 60
});
});
});
}
return bids;
},
getUserSyncs: function (syncOptions, serverResponses) {
const syncs = [];
if (syncOptions.iframeEnabled) {
syncs.push({
type: 'iframe',
url: `${SYNACOR_URL}/usersync/html?src=${REPO_AND_VERSION}`
});
} else {
logWarn('Synacormedia: Please enable iframe based user sync.');
}
return syncs;
}
};

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

```
Module Name: Synacor Media Bidder Adapter
Module Type: Bidder Adapter
Maintainer: eng-demand@synacor.com
```

# Description

The Synacor Media adapter requires setup and approval from Synacor.
Please reach out to your account manager for more information.

# Test Parameters

## Web
```
var adUnits = [{
code: 'test-div',
sizes: [
[300, 250]
],
bids: [{
bidder: "synacormedia",
params: {
seatId: "prebid",
placementId: "81416"
}
}]
},{
code: 'test-div2',
sizes: [
[300, 250]
],
bids: [{
bidder: "synacormedia",
params: {
seatId: "prebid",
placementId: "demo2"
}
}]
}];
```
198 changes: 198 additions & 0 deletions test/spec/modules/synacormediaBidAdapter_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
import { assert, expect } from 'chai';
import { BANNER } from 'src/mediaTypes';
import { spec } from 'modules/synacormediaBidAdapter';

describe('synacormediaBidAdapter ', function () {
describe('isBidRequestValid', function () {
let bid;
beforeEach(function () {
bid = {
params: {
seatId: 'prebid',
placementId: '1234'
}
};
});

it('should return true when params placementId and seatId are truthy', function () {
assert(spec.isBidRequestValid(bid));
});

it('should return false when seatId param is missing', function () {
delete bid.params.seatId;
assert.isFalse(spec.isBidRequestValid(bid));
});
it('should return false when placementId param is missing', function () {
delete bid.params.placementId;
assert.isFalse(spec.isBidRequestValid(bid));
});
it('should return false when params is missing or null', function () {
assert.isFalse(spec.isBidRequestValid({ params: null }));
assert.isFalse(spec.isBidRequestValid({}));
assert.isFalse(spec.isBidRequestValid(null));
});
});
describe('buildRequests', function () {
let validBidRequest = {
bidId: '9876abcd',
sizes: [[300, 250]],
params: {
seatId: 'prebid',
placementId: '1234'
}
};

let bidderRequest = {
auctionId: 'xyz123',
refererInfo: {
referer: 'https://test.com/foo/bar'
}
};

let expectedDataImp = {
banner: {
h: 250,
pos: 0,
w: 300,
},
id: '9876abcd',
tagid: '1234'
};

it('should return valid request when valid bids are used', function () {
let req = spec.buildRequests([validBidRequest], bidderRequest);
expect(req).be.an('object');
expect(req).to.have.property('method', 'POST');
expect(req).to.have.property('url');
expect(req.url).to.contain('//prebid.technoratimedia.com/openrtb/bids/prebid?');
expect(req.data).to.exist.and.to.be.an('object');
expect(req.data.id).to.equal('xyz123');
expect(req.data.imp).to.eql([expectedDataImp]);
});

it('should return multiple bids when multiple valid requests with the same seatId are used', function () {
let secondBidRequest = {
bidId: 'foobar',
sizes: [[300, 600]],
params: {
seatId: validBidRequest.params.seatId,
placementId: '5678'
}
};
let req = spec.buildRequests([validBidRequest, secondBidRequest], bidderRequest);
expect(req).to.exist.and.be.an('object');
expect(req).to.have.property('method', 'POST');
expect(req).to.have.property('url');
expect(req.url).to.contain('//prebid.technoratimedia.com/openrtb/bids/prebid?');
expect(req.data.id).to.equal('xyz123');
expect(req.data.imp).to.eql([expectedDataImp, {
banner: {
h: 600,
pos: 0,
w: 300,
},
id: 'foobar',
tagid: '5678'
}]);
});

it('should return only first bid when different seatIds are used', function () {
let mismatchedSeatBidRequest = {
bidId: 'foobar',
sizes: [[300, 250]],
params: {
seatId: 'somethingelse',
placementId: '5678'
}
};
let req = spec.buildRequests([mismatchedSeatBidRequest, validBidRequest], bidderRequest);
expect(req).to.have.property('method', 'POST');
expect(req).to.have.property('url');
expect(req.url).to.contain('//prebid.technoratimedia.com/openrtb/bids/somethingelse?');
expect(req.data.id).to.equal('xyz123');
expect(req.data.imp).to.eql([
{
banner: {
h: 250,
pos: 0,
w: 300,
},
id: 'foobar',
tagid: '5678'
}
]);
});

it('should not return a request when no valid bid request used', function () {
expect(spec.buildRequests([], bidderRequest)).to.be.undefined;
expect(spec.buildRequests([validBidRequest], null)).to.be.undefined;
});
});

describe('interpretResponse', function () {
let bidResponse = {
id: '10865933907263896~9998~0',
impid: '9876abcd',
price: 0.13,
crid: '1022-250',
adm: '<script src=\"//uat-net.technoratimedia.com/openrtb/tags?AUCTION_ID=${AUCTION_ID}&AUCTION_BID_ID=${AUCTION_BID_ID}&AUCTION_SEAT_ID=${AUCTION_SEAT_ID}&AUCTION_PRICE=${AUCTION_PRICE}&AUCTION_IMP_ID=${AUCTION_IMP_ID}&SOMETHING=${UNSUPPORTED_MACRO}&AUCTION_CURRENCY=${AUCTION_CURRENCY}\"></script>'
};
spec.sizeMap['9876abcd'] = [300, 250];

let serverResponse;
beforeEach(function() {
serverResponse = {
body: {
id: 'abc123',
seatbid: [{
seat: '9998',
bid: [],
}]
}
};
});
it('should return a bid when bid is in the response', function () {
serverResponse.body.seatbid[0].bid.push(bidResponse);
let resp = spec.interpretResponse(serverResponse);
expect(resp).to.be.an('array').that.is.not.empty;
expect(resp[0]).to.eql({
requestId: '9876abcd',
cpm: 0.13,
width: 300,
height: 250,
creativeId: '9998~1022-250',
currency: 'USD',
netRevenue: true,
mediaType: BANNER,
ad: '<script src=\"//uat-net.technoratimedia.com/openrtb/tags?AUCTION_ID=abc123&AUCTION_BID_ID=10865933907263896~9998~0&AUCTION_SEAT_ID=9998&AUCTION_PRICE=0.13&AUCTION_IMP_ID=9876abcd&SOMETHING=${UNSUPPORTED_MACRO}&AUCTION_CURRENCY=USD\"></script>',
ttl: 60
});
});
it('should not return a bid when no bid is in the response', function () {
let resp = spec.interpretResponse(serverResponse);
expect(resp).to.be.an('array').that.is.empty;
});
it('should not return a bid when there is no response body', function () {
expect(spec.interpretResponse({ body: null })).to.not.exist;
expect(spec.interpretResponse({ body: 'some error text' })).to.not.exist;
});
});
describe('getUserSyncs', function () {
it('should return a usersync when iframes is enabled', function () {
let usersyncs = spec.getUserSyncs({
iframeEnabled: true
}, null);
expect(usersyncs).to.be.an('array').that.is.not.empty;
expect(usersyncs[0]).to.have.property('type', 'iframe');
expect(usersyncs[0]).to.have.property('url');
expect(usersyncs[0].url).to.contain('//prebid.technoratimedia.com/usersync/html');
});

it('should not return a usersync when iframes are not enabled', function () {
let usersyncs = spec.getUserSyncs({
pixelEnabled: true
}, null);
expect(usersyncs).to.be.an('array').that.is.empty;
});
});
});