Skip to content

Commit e61b246

Browse files
telariaEngMike Chowla
authored and
Mike Chowla
committed
Added SupplyChain Object support and an onTimeout Callback (prebid#4137)
* - Implemented the 'onTimeout' callback to fire a pixel when there's a timeout. - Added the ability to serialize an schain object according to the description provided here: https://github.com/InteractiveAdvertisingBureau/openrtb/blob/master/supplychainobject.md * some mods to the schain tag generation * - added tests for schain param checking. * - fixed a malformed url for timeouts * - Removed a trailing ',' while generating a schain param.
1 parent 80cbd2c commit e61b246

File tree

2 files changed

+167
-28
lines changed

2 files changed

+167
-28
lines changed

modules/telariaBidAdapter.js

+101-25
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import {VIDEO} from '../src/mediaTypes';
55
import {STATUS} from '../src/constants';
66

77
const BIDDER_CODE = 'telaria';
8-
const ENDPOINT = '.ads.tremorhub.com/ad/tag';
8+
const DOMAIN = 'tremorhub.com';
9+
const TAG_ENDPOINT = `ads.${DOMAIN}/ad/tag`;
10+
const EVENTS_ENDPOINT = `events.${DOMAIN}/diag`;
911

1012
export const spec = {
1113
code: BIDDER_CODE,
@@ -82,7 +84,7 @@ export const spec = {
8284
errorMessage += `: ${bidResult.error}`;
8385
}
8486
utils.logError(errorMessage);
85-
} else if (bidResult.seatbid && bidResult.seatbid.length > 0) {
87+
} else if (!utils.isEmpty(bidResult.seatbid)) {
8688
bidResult.seatbid[0].bid.forEach(tag => {
8789
bids.push(createBid(STATUS.GOOD, bidderRequest, tag, width, height, BIDDER_CODE));
8890
});
@@ -100,11 +102,89 @@ export const spec = {
100102
getUserSyncs: function (syncOptions, serverResponses) {
101103
const syncs = [];
102104
if (syncOptions.pixelEnabled && serverResponses.length) {
103-
try {
104-
serverResponses[0].body.ext.telaria.userSync.forEach(url => syncs.push({type: 'image', url: url}));
105-
} catch (e) {}
105+
(utils.deepAccess(serverResponses, '0.body.ext.telaria.userSync') || []).forEach(url => syncs.push({type: 'image', url: url}));
106106
}
107107
return syncs;
108+
},
109+
110+
/**
111+
* See http://prebid.org/dev-docs/bidder-adaptor.html#registering-on-timeout for detailed semantic.
112+
* @param timeoutData bidRequest
113+
*/
114+
onTimeout: function (timeoutData) {
115+
let url = getTimeoutUrl(timeoutData);
116+
if (url) {
117+
utils.triggerPixel(url);
118+
}
119+
}
120+
};
121+
122+
function getScheme() {
123+
return ((document.location.protocol === 'https:') ? 'https' : 'http') + '://';
124+
}
125+
126+
function getSrcPageUrl(params) {
127+
return (params && params['srcPageUrl']) || encodeURIComponent(document.location.href);
128+
}
129+
130+
function getEncodedValIfNotEmpty(val) {
131+
return !utils.isEmpty(val) ? encodeURIComponent(val) : '';
132+
}
133+
134+
/**
135+
* Converts the schain object to a url param value. Please refer to
136+
* https://github.com/InteractiveAdvertisingBureau/openrtb/blob/master/supplychainobject.md
137+
* (schain for non ORTB section) for more information
138+
* @param schainObject
139+
* @returns {string}
140+
*/
141+
function getSupplyChainAsUrlParam(schainObject) {
142+
if (utils.isEmpty(schainObject)) {
143+
return '';
144+
}
145+
146+
let scStr = `&schain=${schainObject.ver},${schainObject.complete}`;
147+
148+
schainObject.nodes.forEach((node) => {
149+
scStr += '!';
150+
scStr += `${getEncodedValIfNotEmpty(node.asi)},`;
151+
scStr += `${getEncodedValIfNotEmpty(node.sid)},`;
152+
scStr += `${getEncodedValIfNotEmpty(node.hp)},`;
153+
scStr += `${getEncodedValIfNotEmpty(node.rid)},`;
154+
scStr += `${getEncodedValIfNotEmpty(node.name)},`;
155+
scStr += `${getEncodedValIfNotEmpty(node.domain)}`;
156+
});
157+
158+
return scStr;
159+
}
160+
161+
function getUrlParams(params) {
162+
let urlSuffix = '';
163+
164+
if (!utils.isEmpty(params)) {
165+
for (let key in params) {
166+
if (key !== 'schain' && params.hasOwnProperty(key) && !utils.isEmpty(params[key])) {
167+
urlSuffix += `&${key}=${params[key]}`;
168+
}
169+
}
170+
urlSuffix += getSupplyChainAsUrlParam(params['schain']);
171+
}
172+
173+
return urlSuffix;
174+
}
175+
176+
export const getTimeoutUrl = function(timeoutData) {
177+
let params = utils.deepAccess(timeoutData, '0.params.0');
178+
179+
if (!utils.isEmpty(params)) {
180+
let url = `${getScheme()}${EVENTS_ENDPOINT}`;
181+
182+
url += `?srcPageUrl=${getSrcPageUrl(params)}`;
183+
url += `${getUrlParams(params)}`;
184+
185+
url += '&hb=1&evt=TO';
186+
187+
return url;
108188
}
109189
};
110190

@@ -116,9 +196,9 @@ export const spec = {
116196
* @returns {string}
117197
*/
118198
function generateUrl(bid, bidderRequest) {
119-
let playerSize = (bid.mediaTypes && bid.mediaTypes.video && bid.mediaTypes.video.playerSize);
199+
let playerSize = utils.deepAccess(bid, 'mediaTypes.video.playerSize');
120200
if (!playerSize) {
121-
utils.logWarn('Although player size isn\'t required it is highly recommended');
201+
utils.logWarn(`Although player size isn't required it is highly recommended`);
122202
}
123203

124204
let width, height;
@@ -132,45 +212,41 @@ function generateUrl(bid, bidderRequest) {
132212
}
133213
}
134214

135-
if (bid.params.supplyCode && bid.params.adCode) {
136-
let scheme = ((document.location.protocol === 'https:') ? 'https' : 'http') + '://';
137-
let url = scheme + bid.params.supplyCode + ENDPOINT + '?adCode=' + bid.params.adCode;
215+
let supplyCode = utils.deepAccess(bid, 'params.supplyCode');
216+
let adCode = utils.deepAccess(bid, 'params.adCode');
217+
218+
if (supplyCode && adCode) {
219+
let url = `${getScheme()}${supplyCode}.${TAG_ENDPOINT}?adCode=${adCode}`;
138220

139221
if (width) {
140-
url += ('&playerWidth=' + width);
222+
url += (`&playerWidth=${width}`);
141223
}
142224
if (height) {
143-
url += ('&playerHeight=' + height);
225+
url += (`&playerHeight=${height}`);
144226
}
145227

146-
for (let key in bid.params) {
147-
if (bid.params.hasOwnProperty(key) && bid.params[key]) {
148-
url += ('&' + key + '=' + bid.params[key]);
149-
}
150-
}
228+
url += `${getUrlParams(bid.params)}`;
151229

152-
if (!bid.params['srcPageUrl']) {
153-
url += ('&srcPageUrl=' + encodeURIComponent(document.location.href));
154-
}
230+
url += `&srcPageUrl=${getSrcPageUrl(bid.params)}`;
155231

156-
url += ('&transactionId=' + bid.transactionId + '&hb=1');
232+
url += (`&transactionId=${bid.transactionId}`);
157233

158234
if (bidderRequest) {
159235
if (bidderRequest.gdprConsent) {
160236
if (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') {
161-
url += ('&gdpr=' + (bidderRequest.gdprConsent.gdprApplies ? 1 : 0));
237+
url += (`&gdpr=${(bidderRequest.gdprConsent.gdprApplies ? 1 : 0)}`);
162238
}
163239
if (bidderRequest.gdprConsent.consentString) {
164-
url += ('&gdpr_consent=' + bidderRequest.gdprConsent.consentString);
240+
url += (`&gdpr_consent=${bidderRequest.gdprConsent.consentString}`);
165241
}
166242
}
167243

168244
if (bidderRequest.refererInfo && bidderRequest.refererInfo.referer) {
169-
url += ('&referrer=' + encodeURIComponent(bidderRequest.refererInfo.referer));
245+
url += (`&referrer=${encodeURIComponent(bidderRequest.refererInfo.referer)}`);
170246
}
171247
}
172248

173-
return (url + '&fmt=json');
249+
return (url + '&hb=1&fmt=json');
174250
}
175251
}
176252

test/spec/modules/telariaBidAdapter_spec.js

+66-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import {expect} from 'chai';
1+
import {expect, should} from 'chai';
22
import {newBidder} from 'src/adapters/bidderFactory';
3-
import {spec} from 'modules/telariaBidAdapter';
3+
import {spec, getTimeoutUrl} from 'modules/telariaBidAdapter';
44

55
const ENDPOINT = '.ads.tremorhub.com/ad/tag';
66
const AD_CODE = 'ssp-!demo!-lufip';
@@ -16,14 +16,44 @@ const REQUEST = {
1616
},
1717
'mediaType': 'video',
1818
'bids': [{
19-
'bidder': 'tremor',
19+
'bidder': 'telaria',
2020
'params': {
2121
'videoId': 'MyCoolVideo',
2222
'inclSync': true
2323
}
2424
}]
2525
};
2626

27+
const REQUEST_WITH_SCHAIN = [{
28+
'bidder': 'telaria',
29+
'params': {
30+
'videoId': 'MyCoolVideo',
31+
'inclSync': true,
32+
'schain': {
33+
'ver': '1.0',
34+
'complete': 1,
35+
'nodes': [
36+
{
37+
'asi': 'exchange1.com',
38+
'sid': '1234',
39+
'hp': 1,
40+
'rid': 'bid-request-1',
41+
'name': 'publisher',
42+
'domain': 'publisher.com'
43+
},
44+
{
45+
'asi': 'exchange2.com',
46+
'sid': 'abcd',
47+
'hp': 1,
48+
'rid': 'bid-request-2',
49+
'name': 'intermediary',
50+
'domain': 'intermediary.com'
51+
}
52+
]
53+
}
54+
}
55+
}];
56+
2757
const BIDDER_REQUEST = {
2858
'refererInfo': {
2959
'referer': 'www.test.com'
@@ -102,6 +132,8 @@ describe('TelariaAdapter', () => {
102132
}
103133
}];
104134

135+
const schainStub = REQUEST_WITH_SCHAIN;
136+
105137
it('exists and is a function', () => {
106138
expect(spec.buildRequests).to.exist.and.to.be.a('function');
107139
});
@@ -147,6 +179,14 @@ describe('TelariaAdapter', () => {
147179

148180
expect(tempRequest.length).to.equal(0);
149181
});
182+
183+
it('converts the schain object into a tag param', () => {
184+
let tempBid = schainStub;
185+
tempBid[0].params.adCode = 'ssp-!demo!-lufip';
186+
tempBid[0].params.supplyCode = 'ssp-demo-rm6rh';
187+
let builtRequests = spec.buildRequests(tempBid, BIDDER_REQUEST);
188+
expect(builtRequests.length).to.equal(1);
189+
});
150190
});
151191

152192
describe('interpretResponse', () => {
@@ -215,4 +255,27 @@ describe('TelariaAdapter', () => {
215255
expect(urls.length).to.equal(2);
216256
});
217257
});
258+
259+
describe('onTimeout', () => {
260+
const timeoutData = [{
261+
adUnitCode: 'video1',
262+
auctionId: 'd8d239f4-303a-4798-8c8c-dd3151ced4e7',
263+
bidId: '2c749c0101ea92',
264+
bidder: 'telaria',
265+
params: [{
266+
adCode: 'ssp-!demo!-lufip',
267+
supplyCode: 'ssp-demo-rm6rh',
268+
mediaId: 'MyCoolVideo'
269+
}]
270+
}];
271+
272+
it('should return a pixel url', () => {
273+
let url = getTimeoutUrl(timeoutData);
274+
assert(url);
275+
});
276+
277+
it('should fire a pixel', () => {
278+
expect(spec.onTimeout(timeoutData)).to.be.undefined;
279+
});
280+
});
218281
});

0 commit comments

Comments
 (0)