diff --git a/modules/synacormediaBidAdapter.js b/modules/synacormediaBidAdapter.js
index dcaa2c35f4f..3339ecfb04f 100644
--- a/modules/synacormediaBidAdapter.js
+++ b/modules/synacormediaBidAdapter.js
@@ -2,18 +2,26 @@
import { getAdUnitSizes, logWarn } from '../src/utils';
import { registerBidder } from '../src/adapters/bidderFactory';
-import { BANNER } from '../src/mediaTypes';
+import { BANNER, VIDEO } from '../src/mediaTypes';
+import includes from 'core-js/library/fn/array/includes';
const BID_HOST = '//prebid.technoratimedia.com';
const USER_SYNC_HOST = '//ad-cdn.technoratimedia.com';
+const VIDEO_PARAMS = [ 'minduration', 'maxduration' ];
+
export const spec = {
code: 'synacormedia',
- supportedMediaTypes: [ BANNER ],
+ supportedMediaTypes: [ BANNER, VIDEO ],
sizeMap: {},
+ isVideoBid: function(bid) {
+ return bid.mediaTypes !== undefined &&
+ bid.mediaTypes.hasOwnProperty('video');
+ },
isBidRequestValid: function(bid) {
return !!(bid && bid.params && bid.params.placementId && bid.params.seatId);
},
+
buildRequests: function(validBidReqs, bidderRequest) {
if (!validBidReqs || !validBidReqs.length || !bidderRequest) {
return;
@@ -49,20 +57,33 @@ export const spec = {
logWarn(`Synacormedia: there is an invalid POS: ${bid.params.pos}`);
pos = 0;
}
+ let videoOrBannerKey = this.isVideoBid(bid) ? 'video' : 'banner';
getAdUnitSizes(bid).forEach((size, i) => {
- let request = {
- id: bid.bidId + '~' + size[0] + 'x' + size[1],
- tagid: placementId,
- banner: {
- w: size[0],
- h: size[1],
- pos
- }
+ if (!size || size.length != 2) {
+ return;
+ }
+ let size0 = size[0];
+ let size1 = size[1];
+ let imp = {
+ id: `${videoOrBannerKey.substring(0, 1)}${bid.bidId}-${size0}x${size1}`,
+ tagid: placementId
};
if (bidFloor !== null && !isNaN(bidFloor)) {
- request.bidfloor = bidFloor;
+ imp.bidfloor = bidFloor;
+ }
+
+ let videoOrBannerValue = {
+ w: size0,
+ h: size1,
+ pos
+ };
+ if (videoOrBannerKey === 'video' && bid.params.video) {
+ Object.keys(bid.params.video)
+ .filter(param => includes(VIDEO_PARAMS, param) && !isNaN(parseInt(bid.params.video[param], 10)))
+ .forEach(param => videoOrBannerValue[param] = parseInt(bid.params.video[param], 10));
}
- openRtbBidRequest.imp.push(request);
+ imp[videoOrBannerKey] = videoOrBannerValue;
+ openRtbBidRequest.imp.push(imp);
});
});
@@ -79,41 +100,43 @@ export const spec = {
}
},
interpretResponse: function(serverResponse) {
+ var updateMacros = (bid, r) => {
+ return r ? r.replace(/\${AUCTION_PRICE}/g, bid.price) : r;
+ };
+
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 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;
- });
- let [, impid, width, height] = bid.impid.match(/^(.*)~(.*)x(.*)$/);
- bids.push({
+ let creative = updateMacros(bid, bid.adm);
+ let nurl = updateMacros(bid, bid.nurl);
+ let [, impType, impid, width, height] = bid.impid.match(/^([vb])(.*)-(.*)x(.*)$/);
+ let isVideo = impType == 'v';
+ let bidObj = {
requestId: impid,
adId: bid.id.replace(/~/g, '-'),
- cpm: price,
+ cpm: parseFloat(bid.price),
width: parseInt(width, 10),
height: parseInt(height, 10),
- creativeId: seatbid.seat + '~' + bid.crid,
+ creativeId: `${seatbid.seat}_${bid.crid}`,
currency: 'USD',
netRevenue: true,
- mediaType: BANNER,
+ mediaType: isVideo ? VIDEO : BANNER,
ad: creative,
ttl: 60
- });
+ };
+ if (isVideo) {
+ let [, uuid] = nurl.match(/ID=([^&]*)&?/);
+ bidObj.videoCacheKey = encodeURIComponent(uuid);
+ bidObj.vastUrl = nurl;
+ }
+ bids.push(bidObj);
});
});
}
diff --git a/modules/synacormediaBidAdapter.md b/modules/synacormediaBidAdapter.md
index 279cc48bcc1..1f225aa0b2a 100644
--- a/modules/synacormediaBidAdapter.md
+++ b/modules/synacormediaBidAdapter.md
@@ -11,6 +11,13 @@ Maintainer: eng-demand@synacor.com
The Synacor Media adapter requires setup and approval from Synacor.
Please reach out to your account manager for more information.
+### DFP Video Creative
+To use video, setup a `VAST redirect` creative within Google AdManager (DFP) with the following VAST tag URL:
+
+```
+https://track.technoratimedia.com/openrtb/tags?ID=%%PATTERN:hb_cache_id_synacorm%%&AUCTION_PRICE=%%PATTERN:hb_pb_synacormedia%%
+```
+
# Test Parameters
## Web
@@ -24,23 +31,32 @@ Please reach out to your account manager for more information.
bidder: "synacormedia",
params: {
seatId: "prebid",
- placementId: "81416",
- bidfloor: "0.10",
+ placementId: "demo1",
+ bidfloor: 0.10,
pos: 1
}
}]
},{
code: 'test-div2',
- sizes: [
- [300, 250]
- ],
+ mediaType: {
+ video: {
+ context: 'instream',
+ playerSizes: [
+ [300, 250]
+ ],
+ }
+ },
bids: [{
bidder: "synacormedia",
params: {
seatId: "prebid",
- placementId: "demo2"
- bidfloor: "0.10",
- pos: 1
+ placementId: "demo1"
+ bidfloor: 0.20,
+ pos: 1,
+ video: {
+ minduration: 15,
+ maxduration: 30
+ }
}
}]
}];
diff --git a/test/pages/video.html b/test/pages/video.html
index c6a72b6e26b..e040b65fe23 100644
--- a/test/pages/video.html
+++ b/test/pages/video.html
@@ -32,7 +32,7 @@
code: 'video1',
sizes: [640,480],
mediaTypes: {
- video: {context: 'instream'}
+ video: {context: 'instream', playerSize: [640, 480]}
},
bids: [
{
diff --git a/test/spec/modules/synacormediaBidAdapter_spec.js b/test/spec/modules/synacormediaBidAdapter_spec.js
index b91e8cc9fc1..e86bf81a6a7 100644
--- a/test/spec/modules/synacormediaBidAdapter_spec.js
+++ b/test/spec/modules/synacormediaBidAdapter_spec.js
@@ -32,7 +32,114 @@ describe('synacormediaBidAdapter ', function () {
assert.isFalse(spec.isBidRequestValid(null));
});
});
+
+ describe('impression type', function() {
+ let nonVideoReq = {
+ bidId: '9876abcd',
+ sizes: [[300, 250], [300, 600]],
+ params: {
+ seatId: 'prebid',
+ placementId: '1234',
+ bidfloor: '0.50'
+ }
+ };
+
+ let bannerReq = {
+ bidId: '9876abcd',
+ sizes: [[300, 250], [300, 600]],
+ params: {
+ seatId: 'prebid',
+ placementId: '1234',
+ bidfloor: '0.50'
+ },
+ mediaTypes: {
+ banner: {
+ h: 600,
+ pos: 0,
+ w: 300,
+ }
+ },
+ };
+
+ let videoReq = {
+ bidId: '9876abcd',
+ sizes: [[640, 480]],
+ params: {
+ seatId: 'prebid',
+ placementId: '1234',
+ bidfloor: '0.50'
+ },
+ mediaTypes: {
+ video: {
+ context: 'instream',
+ playerSize: [
+ [
+ 640,
+ 480
+ ]
+ ]
+ }
+ },
+ };
+ it('should return correct impression type video/banner', function() {
+ assert.isFalse(spec.isVideoBid(nonVideoReq));
+ assert.isFalse(spec.isVideoBid(bannerReq));
+ assert.isTrue(spec.isVideoBid(videoReq));
+ });
+ });
describe('buildRequests', function () {
+ let validBidRequestVideo = {
+ bidder: 'synacormedia',
+ params: {
+ seatId: 'prebid',
+ placementId: '1234',
+ video: {
+ minduration: 30
+ }
+ },
+ mediaTypes: {
+ video: {
+ context: 'instream',
+ playerSize: [[ 640, 480 ]]
+ }
+ },
+ adUnitCode: 'video1',
+ transactionId: '93e5def8-29aa-4fe8-bd3a-0298c39f189a',
+ sizes: [[ 640, 480 ]],
+ bidId: '2624fabbb078e8',
+ bidderRequestId: '117954d20d7c9c',
+ auctionId: 'defd525f-4f1e-4416-a4cb-ae53be90e706',
+ src: 'client',
+ bidRequestsCount: 1
+ };
+
+ let bidderRequestVideo = {
+ bidderCode: 'synacormedia',
+ auctionId: 'VideoAuctionId124',
+ bidderRequestId: '117954d20d7c9c',
+ auctionStart: 1553624929697,
+ timeout: 700,
+ refererInfo: {
+ referer: 'http://localhost:9999/test/pages/video.html?pbjs_debug=true',
+ reachedTop: true,
+ numIframes: 0,
+ stack: [ 'http://localhost:9999/test/pages/video.html?pbjs_debug=true' ]
+ },
+ start: 1553624929700
+ };
+
+ bidderRequestVideo.bids = validBidRequestVideo;
+ let expectedDataVideo1 = {
+ id: 'v2624fabbb078e8-640x480',
+ tagid: '1234',
+ video: {
+ w: 640,
+ h: 480,
+ pos: 0,
+ minduration: 30
+ }
+ };
+
let validBidRequest = {
bidId: '9876abcd',
sizes: [[300, 250], [300, 600]],
@@ -56,7 +163,7 @@ describe('synacormediaBidAdapter ', function () {
pos: 0,
w: 300,
},
- id: '9876abcd~300x250',
+ id: 'b9876abcd-300x250',
tagid: '1234',
bidfloor: 0.5
};
@@ -66,12 +173,13 @@ describe('synacormediaBidAdapter ', function () {
pos: 0,
w: 300,
},
- id: '9876abcd~300x600',
+ id: 'b9876abcd-300x600',
tagid: '1234',
bidfloor: 0.5
};
it('should return valid request when valid bids are used', function () {
+ // banner test
let req = spec.buildRequests([validBidRequest], bidderRequest);
expect(req).be.an('object');
expect(req).to.have.property('method', 'POST');
@@ -80,6 +188,16 @@ describe('synacormediaBidAdapter ', function () {
expect(req.data).to.exist.and.to.be.an('object');
expect(req.data.id).to.equal('xyz123');
expect(req.data.imp).to.eql([expectedDataImp1, expectedDataImp2]);
+
+ // video test
+ let reqVideo = spec.buildRequests([validBidRequestVideo], bidderRequestVideo);
+ expect(reqVideo).be.an('object');
+ expect(reqVideo).to.have.property('method', 'POST');
+ expect(reqVideo).to.have.property('url');
+ expect(reqVideo.url).to.contain('//prebid.technoratimedia.com/openrtb/bids/prebid?');
+ expect(reqVideo.data).to.exist.and.to.be.an('object');
+ expect(reqVideo.data.id).to.equal('VideoAuctionId124');
+ expect(reqVideo.data.imp).to.eql([expectedDataVideo1]);
});
it('should return multiple bids when multiple valid requests with the same seatId are used', function () {
@@ -104,7 +222,7 @@ describe('synacormediaBidAdapter ', function () {
pos: 0,
w: 300,
},
- id: 'foobar~300x600',
+ id: 'bfoobar-300x600',
tagid: '5678',
bidfloor: 0.5
}]);
@@ -132,7 +250,7 @@ describe('synacormediaBidAdapter ', function () {
pos: 0,
w: 300,
},
- id: 'foobar~300x250',
+ id: 'bfoobar-300x250',
tagid: '5678',
bidfloor: 0.5
}
@@ -161,7 +279,7 @@ describe('synacormediaBidAdapter ', function () {
pos: 0,
w: 300,
},
- id: '9876abcd~300x250',
+ id: 'b9876abcd-300x250',
tagid: '1234',
}
]);
@@ -188,7 +306,7 @@ describe('synacormediaBidAdapter ', function () {
pos: 0,
w: 300,
},
- id: '9876abcd~300x250',
+ id: 'b9876abcd-300x250',
tagid: '1234',
}
]);
@@ -216,7 +334,7 @@ describe('synacormediaBidAdapter ', function () {
w: 300,
pos: 1,
},
- id: '9876abcd~300x250',
+ id: 'b9876abcd-300x250',
tagid: '1234'
}
]);
@@ -243,7 +361,7 @@ describe('synacormediaBidAdapter ', function () {
w: 300,
pos: 0,
},
- id: '9876abcd~300x250',
+ id: 'b9876abcd-300x250',
tagid: '1234'
}
]);
@@ -252,22 +370,58 @@ describe('synacormediaBidAdapter ', function () {
expect(spec.buildRequests([], bidderRequest)).to.be.undefined;
expect(spec.buildRequests([validBidRequest], null)).to.be.undefined;
});
+
+ it('should return empty impression when there is no valid sizes in bidrequest', function() {
+ let validBidReqWithoutSize = {
+ bidId: '9876abcd',
+ sizes: [],
+ params: {
+ seatId: 'prebid',
+ placementId: '1234',
+ bidfloor: '0.50'
+ }
+ };
+
+ let validBidReqInvalidSize = {
+ bidId: '9876abcd',
+ sizes: [[300]],
+ params: {
+ seatId: 'prebid',
+ placementId: '1234',
+ bidfloor: '0.50'
+ }
+ };
+
+ let bidderRequest = {
+ auctionId: 'xyz123',
+ refererInfo: {
+ referer: 'https://test.com/foo/bar'
+ }
+ };
+
+ let req = spec.buildRequests([validBidReqWithoutSize], bidderRequest);
+ assert.isUndefined(req);
+ req = spec.buildRequests([validBidReqInvalidSize], bidderRequest);
+ assert.isUndefined(req);
+ });
});
describe('interpretResponse', function () {
let bidResponse = {
id: '10865933907263896~9998~0',
- impid: '9876abcd~300x250',
+ impid: 'b9876abcd-300x250',
price: 0.13,
crid: '1022-250',
- adm: ''
+ adm: '',
+ nurl: '//uat-net.technoratimedia.com/openrtb/tags?ID=k5JkFVQ1RJT05fSU1QX0lEPXYyZjczN&AUCTION_PRICE=${AUCTION_PRICE}'
};
let bidResponse2 = {
id: '10865933907263800~9999~0',
- impid: '9876abcd~300x600',
+ impid: 'b9876abcd-300x600',
price: 1.99,
crid: '9993-013',
- adm: ''
+ adm: '',
+ nurl: '//uat-net.technoratimedia.com/openrtb/tags?ID=OTk5OX4wJkFVQ1RJT05fU0VBVF9JR&AUCTION_PRICE=${AUCTION_PRICE}'
};
let serverResponse;
@@ -282,6 +436,52 @@ describe('synacormediaBidAdapter ', function () {
}
};
});
+
+ it('should return 1 video bid when 1 bid is in the video response', function () {
+ let serverRespVideo = {
+ body: {
+ id: 'abcd1234',
+ seatbid: [
+ {
+ bid: [
+ {
+ id: '11339128001692337~9999~0',
+ impid: 'v2da7322b2df61f-640x480',
+ price: 0.45,
+ nurl: 'https://uat-net.technoratimedia.com/openrtb/tags?ID=QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk&AUCTION_PRICE=${AUCTION_PRICE}',
+ adm: '\n\n\n\nSynacor Media Ad Server - 9999\nhttps://uat-net.technoratimedia.com/openrtb/tags?ID=QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk&AUCTION_PRICE=${AUCTION_PRICE}\n\n\n',
+ adomain: [ 'psacentral.org' ],
+ cid: 'bidder-crid',
+ crid: 'bidder-cid',
+ cat: []
+ }
+ ],
+ seat: '9999'
+ }
+ ]
+ }
+ };
+
+ // serverResponse.body.seatbid[0].bid.push(bidResponse);
+ let resp = spec.interpretResponse(serverRespVideo);
+ expect(resp).to.be.an('array').to.have.lengthOf(1);
+ expect(resp[0]).to.eql({
+ requestId: '2da7322b2df61f',
+ adId: '11339128001692337-9999-0',
+ cpm: 0.45,
+ width: 640,
+ height: 480,
+ creativeId: '9999_bidder-cid',
+ currency: 'USD',
+ netRevenue: true,
+ mediaType: 'video',
+ ad: '\n\n\n\nSynacor Media Ad Server - 9999\nhttps://uat-net.technoratimedia.com/openrtb/tags?ID=QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk&AUCTION_PRICE=0.45\n\n\n',
+ ttl: 60,
+ videoCacheKey: 'QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk',
+ vastUrl: 'https://uat-net.technoratimedia.com/openrtb/tags?ID=QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk&AUCTION_PRICE=0.45'
+ });
+ });
+
it('should return 1 bid when 1 bid is in the response', function () {
serverResponse.body.seatbid[0].bid.push(bidResponse);
let resp = spec.interpretResponse(serverResponse);
@@ -292,11 +492,11 @@ describe('synacormediaBidAdapter ', function () {
cpm: 0.13,
width: 300,
height: 250,
- creativeId: '9998~1022-250',
+ creativeId: '9998_1022-250',
currency: 'USD',
netRevenue: true,
mediaType: BANNER,
- ad: '',
+ ad: '',
ttl: 60
});
});
@@ -315,31 +515,34 @@ describe('synacormediaBidAdapter ', function () {
cpm: 0.13,
width: 300,
height: 250,
- creativeId: '9998~1022-250',
+ creativeId: '9998_1022-250',
currency: 'USD',
netRevenue: true,
mediaType: BANNER,
- ad: '',
+ ad: '',
ttl: 60
});
+
expect(resp[1]).to.eql({
requestId: '9876abcd',
adId: '10865933907263800-9999-0',
cpm: 1.99,
width: 300,
height: 600,
- creativeId: '9999~9993-013',
+ creativeId: '9999_9993-013',
currency: 'USD',
netRevenue: true,
mediaType: BANNER,
- ad: '',
+ ad: '',
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;