diff --git a/modules/tripleliftBidAdapter.js b/modules/tripleliftBidAdapter.js
index df4f9a9ba38..a5a93024ff9 100644
--- a/modules/tripleliftBidAdapter.js
+++ b/modules/tripleliftBidAdapter.js
@@ -8,7 +8,7 @@ const GVLID = 28;
const BIDDER_CODE = 'triplelift';
const STR_ENDPOINT = 'https://tlx.3lift.com/header/auction?';
const BANNER_TIME_TO_LIVE = 300;
-const INSTREAM_TIME_TO_LIVE = 3600;
+const VIDEO_TIME_TO_LIVE = 3600;
let gdprApplies = true;
let consentString = null;
export const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE});
@@ -120,12 +120,15 @@ function _buildPostBody(bidRequests, bidderRequest) {
tagid: bidRequest.params.inventoryCode,
floor: _getFloor(bidRequest)
};
- // remove the else to support multi-imp
- if (_isInstreamBidRequest(bidRequest)) {
+ // Check for video bidrequest
+ if (_isVideoBidRequest(bidRequest)) {
imp.video = _getORTBVideo(bidRequest);
- } else if (bidRequest.mediaTypes.banner) {
+ }
+ // append banner if applicable and request is not for instream
+ if (bidRequest.mediaTypes.banner && !_isInstream(bidRequest)) {
imp.banner = { format: _sizes(bidRequest.sizes) };
- };
+ }
+
if (!isEmpty(bidRequest.ortb2Imp)) {
imp.fpd = _getAdUnitFpd(bidRequest.ortb2Imp);
}
@@ -153,22 +156,41 @@ function _buildPostBody(bidRequests, bidderRequest) {
return data;
}
-function _isInstreamBidRequest(bidRequest) {
- if (!bidRequest.mediaTypes.video) return false;
- if (!bidRequest.mediaTypes.video.context) return false;
- if (bidRequest.mediaTypes.video.context.toLowerCase() === 'instream') {
- return true;
- } else {
- return false;
- }
+function _isVideoBidRequest(bidRequest) {
+ return _isValidVideoObject(bidRequest) && (_isInstream(bidRequest) || _isOutstream(bidRequest));
+}
+
+function _isOutstream(bidRequest) {
+ return _isValidVideoObject(bidRequest) && bidRequest.mediaTypes.video.context.toLowerCase() === 'outstream';
+}
+
+function _isInstream(bidRequest) {
+ return _isValidVideoObject(bidRequest) && bidRequest.mediaTypes.video.context.toLowerCase() === 'instream';
+}
+
+function _isValidVideoObject(bidRequest) {
+ return bidRequest.mediaTypes.video && bidRequest.mediaTypes.video.context;
}
function _getORTBVideo(bidRequest) {
// give precedent to mediaTypes.video
let video = { ...bidRequest.params.video, ...bidRequest.mediaTypes.video };
- if (!video.w) video.w = video.playerSize[0][0];
- if (!video.h) video.h = video.playerSize[0][1];
+ try {
+ if (!video.w) video.w = video.playerSize[0][0];
+ if (!video.h) video.h = video.playerSize[0][1];
+ } catch (err) {
+ logWarn('Video size not defined', err);
+ }
if (video.context === 'instream') video.placement = 1;
+ if (video.context === 'outstream') {
+ if (!video.placement) {
+ video.placement = 3
+ } else if ([3, 4, 5].indexOf(video.placement) === -1) {
+ logMessage(`video.placement value of ${video.placement} is invalid for outstream context. Setting placement to 3`)
+ video.placement = 3
+ }
+ }
+
// clean up oRTB object
delete video.playerSize;
return video;
@@ -180,7 +202,7 @@ function _getFloor (bid) {
try {
const floorInfo = bid.getFloor({
currency: 'USD',
- mediaType: _isInstreamBidRequest(bid) ? 'video' : 'banner',
+ mediaType: _isVideoBidRequest(bid) ? 'video' : 'banner',
size: '*'
});
if (typeof floorInfo === 'object' &&
@@ -366,10 +388,10 @@ function _buildResponseObject(bidderRequest, bid) {
meta: {}
};
- if (_isInstreamBidRequest(breq)) {
+ if (_isVideoBidRequest(breq) && bid.media_type === 'video') {
bidResponse.vastXml = bid.ad;
bidResponse.mediaType = 'video';
- bidResponse.ttl = INSTREAM_TIME_TO_LIVE;
+ bidResponse.ttl = VIDEO_TIME_TO_LIVE;
};
if (bid.advertiser_name) {
@@ -381,7 +403,11 @@ function _buildResponseObject(bidderRequest, bid) {
}
if (bid.tl_source && bid.tl_source == 'hdx') {
- bidResponse.meta.mediaType = 'banner';
+ if (_isVideoBidRequest(breq) && bid.media_type === 'video') {
+ bidResponse.meta.mediaType = 'video'
+ } else {
+ bidResponse.meta.mediaType = 'banner'
+ }
}
if (bid.tl_source && bid.tl_source == 'tlx') {
diff --git a/test/spec/modules/tripleliftBidAdapter_spec.js b/test/spec/modules/tripleliftBidAdapter_spec.js
index b0269aaf077..bfcfce1ccb4 100644
--- a/test/spec/modules/tripleliftBidAdapter_spec.js
+++ b/test/spec/modules/tripleliftBidAdapter_spec.js
@@ -345,6 +345,209 @@ describe('triplelift adapter', function () {
auctionId: '1d1a030790a475',
userId: {},
schain,
+ },
+ // outstream video only
+ {
+ bidder: 'triplelift',
+ params: {
+ inventoryCode: 'outstream_test',
+ floor: 1.0,
+ video: {
+ mimes: ['video/mp4'],
+ maxduration: 30,
+ minduration: 6,
+ w: 640,
+ h: 480
+ }
+ },
+ mediaTypes: {
+ video: {
+ context: 'outstream',
+ playerSize: [640, 480]
+ }
+ },
+ adUnitCode: 'adunit-code-outstream',
+ sizes: [[300, 250], [300, 600], [1, 1, 1], ['flex']],
+ bidId: '30b31c1838de1e',
+ bidderRequestId: '22edbae2733bf6',
+ auctionId: '1d1a030790a475',
+ userId: {},
+ schain,
+ },
+ // banner and incomplete outstream (missing size)
+ {
+ bidder: 'triplelift',
+ params: {
+ inventoryCode: 'outstream_test',
+ floor: 1.0,
+ video: {
+ mimes: ['video/mp4'],
+ maxduration: 30,
+ minduration: 6
+ }
+ },
+ mediaTypes: {
+ video: {
+ context: 'outstream'
+ },
+ banner: {
+ sizes: [
+ [970, 250],
+ [1, 1]
+ ]
+ }
+ },
+ adUnitCode: 'adunit-code-instream',
+ sizes: [[300, 250], [300, 600], [1, 1, 1], ['flex']],
+ bidId: '30b31c1838de1e',
+ bidderRequestId: '22edbae2733bf6',
+ auctionId: '1d1a030790a475',
+ userId: {},
+ schain,
+ },
+ // outstream video; valid placement
+ {
+ bidder: 'triplelift',
+ params: {
+ inventoryCode: 'outstream_test',
+ floor: 1.0,
+ video: {
+ mimes: ['video/mp4'],
+ maxduration: 30,
+ minduration: 6,
+ w: 640,
+ h: 480
+ }
+ },
+ mediaTypes: {
+ video: {
+ context: 'outstream',
+ playerSize: [640, 480],
+ placement: 3
+ }
+ },
+ adUnitCode: 'adunit-code-instream',
+ sizes: [[300, 250], [300, 600], [1, 1, 1], ['flex']],
+ bidId: '30b31c1838de1e',
+ bidderRequestId: '22edbae2733bf6',
+ auctionId: '1d1a030790a475',
+ userId: {},
+ schain,
+ },
+ // outstream video; valid placement
+ {
+ bidder: 'triplelift',
+ params: {
+ inventoryCode: 'outstream_test',
+ floor: 1.0,
+ video: {
+ mimes: ['video/mp4'],
+ maxduration: 30,
+ minduration: 6,
+ w: 640,
+ h: 480
+ }
+ },
+ mediaTypes: {
+ video: {
+ context: 'outstream',
+ playerSize: [640, 480],
+ placement: 4
+ }
+ },
+ adUnitCode: 'adunit-code-instream',
+ sizes: [[300, 250], [300, 600], [1, 1, 1], ['flex']],
+ bidId: '30b31c1838de1e',
+ bidderRequestId: '22edbae2733bf6',
+ auctionId: '1d1a030790a475',
+ userId: {},
+ schain,
+ },
+ // outstream video; valid placement
+ {
+ bidder: 'triplelift',
+ params: {
+ inventoryCode: 'outstream_test',
+ floor: 1.0,
+ video: {
+ mimes: ['video/mp4'],
+ maxduration: 30,
+ minduration: 6,
+ w: 640,
+ h: 480
+ }
+ },
+ mediaTypes: {
+ video: {
+ context: 'outstream',
+ playerSize: [640, 480],
+ placement: 5
+ }
+ },
+ adUnitCode: 'adunit-code-instream',
+ sizes: [[300, 250], [300, 600], [1, 1, 1], ['flex']],
+ bidId: '30b31c1838de1e',
+ bidderRequestId: '22edbae2733bf6',
+ auctionId: '1d1a030790a475',
+ userId: {},
+ schain,
+ },
+ // outstream video; undefined placement
+ {
+ bidder: 'triplelift',
+ params: {
+ inventoryCode: 'outstream_test',
+ floor: 1.0,
+ video: {
+ mimes: ['video/mp4'],
+ maxduration: 30,
+ minduration: 6,
+ w: 640,
+ h: 480
+ }
+ },
+ mediaTypes: {
+ video: {
+ context: 'outstream',
+ playerSize: [640, 480]
+ }
+ },
+ adUnitCode: 'adunit-code-instream',
+ sizes: [[300, 250], [300, 600], [1, 1, 1], ['flex']],
+ bidId: '30b31c1838de1e',
+ bidderRequestId: '22edbae2733bf6',
+ auctionId: '1d1a030790a475',
+ userId: {},
+ schain,
+ },
+ // outstream video; invalid placement
+ {
+ bidder: 'triplelift',
+ params: {
+ inventoryCode: 'outstream_test',
+ floor: 1.0,
+ video: {
+ mimes: ['video/mp4'],
+ maxduration: 30,
+ minduration: 6,
+ w: 640,
+ h: 480
+ }
+ },
+ mediaTypes: {
+ video: {
+ context: 'outstream',
+ playerSize: [640, 480],
+ placement: 6
+ }
+ },
+ adUnitCode: 'adunit-code-instream',
+ sizes: [[300, 250], [300, 600], [1, 1, 1], ['flex']],
+ bidId: '30b31c1838de1e',
+ bidderRequestId: '22edbae2733bf6',
+ auctionId: '1d1a030790a475',
+ userId: {},
+ schain,
}
];
@@ -415,13 +618,16 @@ describe('triplelift adapter', function () {
expect(payload.imp[0].tagid).to.equal('12345');
expect(payload.imp[0].floor).to.equal(1.0);
expect(payload.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}, {w: 300, h: 600}]);
+ // instream
expect(payload.imp[1].tagid).to.equal('insteam_test');
expect(payload.imp[1].floor).to.equal(1.0);
expect(payload.imp[1].video).to.exist.and.to.be.a('object');
+ expect(payload.imp[1].video.placement).to.equal(1);
// banner and outstream video
- expect(payload.imp[2]).to.not.have.property('video');
+ expect(payload.imp[2]).to.have.property('video');
expect(payload.imp[2]).to.have.property('banner');
expect(payload.imp[2].banner.format).to.deep.equal([{w: 300, h: 250}, {w: 300, h: 600}]);
+ expect(payload.imp[2].video).to.deep.equal({'mimes': ['video/mp4'], 'maxduration': 30, 'minduration': 6, 'w': 640, 'h': 480, 'context': 'outstream', 'placement': 3});
// banner and incomplete video
expect(payload.imp[3]).to.not.have.property('video');
expect(payload.imp[3]).to.have.property('banner');
@@ -434,10 +640,51 @@ describe('triplelift adapter', function () {
expect(payload.imp[5]).to.not.have.property('banner');
expect(payload.imp[5]).to.have.property('video');
expect(payload.imp[5].video).to.exist.and.to.be.a('object');
+ expect(payload.imp[5].video.placement).to.equal(1);
// banner and outream video and native
- expect(payload.imp[6]).to.not.have.property('video');
+ expect(payload.imp[6]).to.have.property('video');
expect(payload.imp[6]).to.have.property('banner');
expect(payload.imp[6].banner.format).to.deep.equal([{w: 300, h: 250}, {w: 300, h: 600}]);
+ expect(payload.imp[6].video).to.deep.equal({'mimes': ['video/mp4'], 'maxduration': 30, 'minduration': 6, 'w': 640, 'h': 480, 'context': 'outstream', 'placement': 3});
+ // outstream video only
+ expect(payload.imp[7]).to.have.property('video');
+ expect(payload.imp[7]).to.not.have.property('banner');
+ expect(payload.imp[7].video).to.deep.equal({'mimes': ['video/mp4'], 'maxduration': 30, 'minduration': 6, 'w': 640, 'h': 480, 'context': 'outstream', 'placement': 3});
+ // banner and incomplete outstream (missing size); video request is permitted so banner can still monetize
+ expect(payload.imp[8]).to.have.property('video');
+ expect(payload.imp[8]).to.have.property('banner');
+ expect(payload.imp[8].banner.format).to.deep.equal([{w: 300, h: 250}, {w: 300, h: 600}]);
+ expect(payload.imp[8].video).to.deep.equal({'mimes': ['video/mp4'], 'maxduration': 30, 'minduration': 6, 'context': 'outstream', 'placement': 3});
+ });
+
+ it('should check for valid outstream placement values', function () {
+ const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest);
+ const payload = request.data;
+ // outstream video; valid placement
+ expect(payload.imp[9]).to.not.have.property('banner');
+ expect(payload.imp[9]).to.have.property('video');
+ expect(payload.imp[9].video).to.exist.and.to.be.a('object');
+ expect(payload.imp[9].video.placement).to.equal(3);
+ // outstream video; valid placement
+ expect(payload.imp[10]).to.not.have.property('banner');
+ expect(payload.imp[10]).to.have.property('video');
+ expect(payload.imp[10].video).to.exist.and.to.be.a('object');
+ expect(payload.imp[10].video.placement).to.equal(4);
+ // outstream video; valid placement
+ expect(payload.imp[11]).to.not.have.property('banner');
+ expect(payload.imp[11]).to.have.property('video');
+ expect(payload.imp[11].video).to.exist.and.to.be.a('object');
+ expect(payload.imp[11].video.placement).to.equal(5);
+ // outstream video; undefined placement
+ expect(payload.imp[12]).to.not.have.property('banner');
+ expect(payload.imp[12]).to.have.property('video');
+ expect(payload.imp[12].video).to.exist.and.to.be.a('object');
+ expect(payload.imp[12].video.placement).to.equal(3);
+ // outstream video; invalid placement
+ expect(payload.imp[13]).to.not.have.property('banner');
+ expect(payload.imp[13]).to.have.property('video');
+ expect(payload.imp[13].video).to.exist.and.to.be.a('object');
+ expect(payload.imp[13].video.placement).to.equal(3);
});
it('should add tdid to the payload if included', function () {
@@ -913,14 +1160,40 @@ describe('triplelift adapter', function () {
iurl: 'https://s.adroll.com/a/IYR/N36/IYRN366MFVDITBAGNNT5U6.jpg',
tl_source: 'tlx',
advertiser_name: 'fake advertiser name',
- adomain: ['basspro.com', 'internetalerts.org']
+ adomain: ['basspro.com', 'internetalerts.org'],
+ media_type: 'banner'
},
{
imp_id: 1,
crid: '10092_76480_i2j6qm8u',
cpm: 9.99,
- ad: 'The Trade Desk',
- tlx_source: 'hdx'
+ ad: 'The Trade Desk',
+ tl_source: 'hdx',
+ media_type: 'video'
+ },
+ // video bid on banner+outstream request
+ {
+ imp_id: 2,
+ crid: '5989_33264_352817187',
+ cpm: 20,
+ ad: '\n \n \t',
+ tl_source: 'hdx',
+ advertiser_name: 'zennioptical.com',
+ adomain: ['zennioptical.com'],
+ media_type: 'video'
+ },
+ // banner bid on banner+outstream request
+ {
+ imp_id: 3,
+ crid: '5989_33264_352817187',
+ cpm: 20,
+ width: 970,
+ height: 250,
+ ad: 'ad-markup',
+ tl_source: 'hdx',
+ advertiser_name: 'zennioptical.com',
+ adomain: ['zennioptical.com'],
+ media_type: 'banner'
}
]
}
@@ -946,13 +1219,13 @@ describe('triplelift adapter', function () {
]
}
},
- bidId: '30b31c1838de1e',
+ bidId: '30b31c1838de1e'
},
{
imp_id: 1,
crid: '10092_76480_i2j6qm8u',
cpm: 9.99,
- ad: 'The Trade Desk',
+ ad: 'The Trade Desk',
tlx_source: 'hdx',
mediaTypes: {
video: {
@@ -960,7 +1233,89 @@ describe('triplelift adapter', function () {
playerSize: [640, 480]
}
},
- bidId: '30b31c1838de1e',
+ bidId: '30b31c1838de1e'
+ },
+ // banner and outstream
+ {
+ bidder: 'triplelift',
+ params: {
+ inventoryCode: 'testing_desktop_outstream',
+ floor: 1
+ },
+ nativeParams: {},
+ mediaTypes: {
+ video: {
+ context: 'outstream',
+ playerSize: [[640, 480]],
+ mimes: ['video/mp4'],
+ protocols: [1, 2, 3, 4, 5, 6, 7, 8],
+ playbackmethod: [2],
+ skip: 1
+ },
+ banner: {
+ sizes: [
+ [728, 90],
+ [970, 250],
+ [970, 90]
+ ]
+ },
+ native: {}
+ },
+ adUnitCode: 'video-outstream',
+ transactionId: '135061c3-f546-4e28-8a07-44c2fb58a958',
+ sizes: [
+ [728, 90],
+ [970, 250],
+ [970, 90]
+ ],
+ bidId: '73edc0ba8de203',
+ bidderRequestId: '3d81143328560b',
+ auctionId: 'f6427dc0-b954-4010-a76c-d498380796a2',
+ src: 'client',
+ bidRequestsCount: 2,
+ bidderRequestsCount: 2,
+ bidderWinsCount: 0
+ },
+ // banner and outstream
+ {
+ bidder: 'triplelift',
+ params: {
+ inventoryCode: 'testing_desktop_outstream',
+ floor: 1
+ },
+ nativeParams: {},
+ mediaTypes: {
+ video: {
+ context: 'outstream',
+ playerSize: [[640, 480]],
+ mimes: ['video/mp4'],
+ protocols: [1, 2, 3, 4, 5, 6, 7, 8],
+ playbackmethod: [2],
+ skip: 1
+ },
+ banner: {
+ sizes: [
+ [728, 90],
+ [970, 250],
+ [970, 90]
+ ]
+ },
+ native: {}
+ },
+ adUnitCode: 'video-outstream',
+ transactionId: '135061c3-f546-4e28-8a07-44c2fb58a958',
+ sizes: [
+ [728, 90],
+ [970, 250],
+ [970, 90]
+ ],
+ bidId: '73edc0ba8de203',
+ bidderRequestId: '3d81143328560b',
+ auctionId: 'f6427dc0-b954-4010-a76c-d498380796a2',
+ src: 'client',
+ bidRequestsCount: 2,
+ bidderRequestsCount: 2,
+ bidderWinsCount: 0
}
],
refererInfo: {
@@ -1007,16 +1362,29 @@ describe('triplelift adapter', function () {
}
];
let result = tripleliftAdapterSpec.interpretResponse(response, {bidderRequest});
- expect(result).to.have.length(2);
+ expect(result).to.have.length(4);
expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0]));
expect(Object.keys(result[1])).to.have.members(Object.keys(expectedResponse[1]));
expect(result[0].ttl).to.equal(300);
expect(result[1].ttl).to.equal(3600);
});
+ it('should identify format of bid and respond accordingly', function() {
+ let result = tripleliftAdapterSpec.interpretResponse(response, {bidderRequest});
+ expect(result[0].meta.mediaType).to.equal('native');
+ expect(result[1].mediaType).to.equal('video');
+ expect(result[1].meta.mediaType).to.equal('video');
+ // video bid on banner+outstream request
+ expect(result[2].mediaType).to.equal('video');
+ expect(result[2].meta.mediaType).to.equal('video');
+ expect(result[2].vastXml).to.include('aid=148508128401385324170&inv_code=testing_mobile_outstream');
+ // banner bid on banner+outstream request
+ expect(result[3].meta.mediaType).to.equal('banner');
+ })
+
it('should return multiple responses to support SRA', function () {
let result = tripleliftAdapterSpec.interpretResponse(response, {bidderRequest});
- expect(result).to.have.length(2);
+ expect(result).to.have.length(4);
});
it('should include the advertiser name in the meta field if available', function () {