diff --git a/modules/yieldlabBidAdapter.js b/modules/yieldlabBidAdapter.js index c05ecbc15fc..e992dea0c65 100644 --- a/modules/yieldlabBidAdapter.js +++ b/modules/yieldlabBidAdapter.js @@ -3,6 +3,7 @@ import { registerBidder } from '../src/adapters/bidderFactory.js' import find from 'core-js-pure/features/array/find.js' import { VIDEO, BANNER } from '../src/mediaTypes.js' import { Renderer } from '../src/Renderer.js' +import { config } from '../src/config.js'; const ENDPOINT = 'https://ad.yieldlab.net' const BIDDER_CODE = 'yieldlab' @@ -52,6 +53,11 @@ export const spec = { if (bid.schain && utils.isPlainObject(bid.schain) && Array.isArray(bid.schain.nodes)) { query.schain = createSchainString(bid.schain) } + + const iabContent = getContentObject(bid) + if (iabContent) { + query.iab_content = createIabContentString(iabContent) + } }) if (bidderRequest) { @@ -105,6 +111,7 @@ export const spec = { const gdprApplies = reqParams.gdpr ? '&gdpr=' + reqParams.gdpr : '' const gdprConsent = reqParams.consent ? '&consent=' + reqParams.consent : '' const pvId = matchedBid.pvid !== undefined ? '&pvid=' + matchedBid.pvid : '' + const iabContent = reqParams.iab_content ? '&iab_content=' + reqParams.iab_content : '' const bidResponse = { requestId: bidRequest.bidId, @@ -117,7 +124,7 @@ export const spec = { netRevenue: false, ttl: BID_RESPONSE_TTL_SEC, referrer: '', - ad: ``, + ad: ``, meta: { advertiserDomains: (matchedBid.advertiser) ? matchedBid.advertiser : 'n/a' } @@ -130,7 +137,7 @@ export const spec = { bidResponse.height = playersize[1] } bidResponse.mediaType = VIDEO - bidResponse.vastUrl = `${ENDPOINT}/d/${matchedBid.id}/${bidRequest.params.supplyId}/?ts=${timestamp}${extId}${gdprApplies}${gdprConsent}${pvId}` + bidResponse.vastUrl = `${ENDPOINT}/d/${matchedBid.id}/${bidRequest.params.supplyId}/?ts=${timestamp}${extId}${gdprApplies}${gdprConsent}${pvId}${iabContent}` if (isOutstream(bidRequest)) { const renderer = Renderer.install({ id: bidRequest.bidId, @@ -211,7 +218,7 @@ function createQueryString (obj) { for (var p in obj) { if (obj.hasOwnProperty(p)) { let val = obj[p] - if (p !== 'schain') { + if (p !== 'schain' && p !== 'iab_content') { str.push(encodeURIComponent(p) + '=' + encodeURIComponent(val)) } else { str.push(p + '=' + val) @@ -253,6 +260,44 @@ function createSchainString (schain) { return `${ver},${complete}${nodesString}` } +/** + * Get content object from bid request + * First get content from bidder params; + * If not provided in bidder params, get from first party data under 'ortb2.site.content' or 'ortb2.app.content' + * @param {Object} bid + * @returns {Object} + */ +function getContentObject(bid) { + if (bid.params.iabContent && utils.isPlainObject(bid.params.iabContent)) { + return bid.params.iabContent + } + + const globalContent = config.getConfig('ortb2.site') ? config.getConfig('ortb2.site.content') + : config.getConfig('ortb2.app.content') + if (globalContent && utils.isPlainObject(globalContent)) { + return globalContent + } + return undefined +} + +/** + * Creates a string for iab_content object + * @param {Object} iabContent + * @returns {String} + */ +function createIabContentString(iabContent) { + const arrKeys = ['keywords', 'cat'] + let str = [] + for (let key in iabContent) { + if (iabContent.hasOwnProperty(key)) { + const value = (arrKeys.indexOf(key) !== -1 && Array.isArray(iabContent[key])) + ? iabContent[key].map(node => encodeURIComponent(node)).join('|') : encodeURIComponent(iabContent[key]) + str.push(''.concat(key, ':', value)) + } + } + return encodeURIComponent(str.join(',')) +} + /** * Encodes URI Component with exlamation mark included. Needed for schain object. * @param {String} str diff --git a/modules/yieldlabBidAdapter.md b/modules/yieldlabBidAdapter.md index a7a3f2715dc..e3360ab10be 100644 --- a/modules/yieldlabBidAdapter.md +++ b/modules/yieldlabBidAdapter.md @@ -25,7 +25,22 @@ Module that connects to Yieldlab's demand sources key1: "value1", key2: "value2" }, - extId: "abc" + extId: "abc", + iabContent: { + id: "some_id", + episode: "1", + title: "some title", + series: "some series", + season: "s1", + artist: "John Doe", + genre: "some genre", + isrc: "CC-XXX-YY-NNNNN", + url: "http://foo_url.de", + cat: ["IAB1-1", "IAB1-2", "IAB2-10"], + context: "7", + keywords: ["k1", "k2"], + live: "0" + } } }] }, { diff --git a/test/spec/modules/yieldlabBidAdapter_spec.js b/test/spec/modules/yieldlabBidAdapter_spec.js index d150646851f..698bfb92888 100644 --- a/test/spec/modules/yieldlabBidAdapter_spec.js +++ b/test/spec/modules/yieldlabBidAdapter_spec.js @@ -1,3 +1,4 @@ +import { config } from 'src/config.js'; import { expect } from 'chai' import { spec } from 'modules/yieldlabBidAdapter.js' import { newBidder } from 'src/adapters/bidderFactory.js' @@ -16,7 +17,22 @@ const REQUEST = { 'extraParam': true, 'foo': 'bar' }, - 'extId': 'abc' + 'extId': 'abc', + 'iabContent': { + 'id': 'foo_id', + 'episode': '99', + 'title': 'foo_title,bar_title', + 'series': 'foo_series', + 'season': 's1', + 'artist': 'foo bar', + 'genre': 'baz', + 'isrc': 'CC-XXX-YY-NNNNN', + 'url': 'http://foo_url.de', + 'cat': ['cat1', 'cat2,ppp', 'cat3|||//'], + 'context': '7', + 'keywords': ['k1,', 'k2..'], + 'live': '0' + } }, 'bidderRequestId': '143346cf0f1731', 'auctionId': '2e41f65424c87c', @@ -86,6 +102,10 @@ const REQPARAMS_GDPR = Object.assign({}, REQPARAMS, { consent: 'BN5lERiOMYEdiAKAWXEND1AAAAE6DABACMA' }) +const REQPARAMS_IAB_CONTENT = Object.assign({}, REQPARAMS, { + iab_content: 'id%3Afoo_id%2Cepisode%3A99%2Ctitle%3Afoo_title%252Cbar_title%2Cseries%3Afoo_series%2Cseason%3As1%2Cartist%3Afoo%2520bar%2Cgenre%3Abaz%2Cisrc%3ACC-XXX-YY-NNNNN%2Curl%3Ahttp%253A%252F%252Ffoo_url.de%2Ccat%3Acat1%7Ccat2%252Cppp%7Ccat3%257C%257C%257C%252F%252F%2Ccontext%3A7%2Ckeywords%3Ak1%252C%7Ck2..%2Clive%3A0' +}) + describe('yieldlabBidAdapter', function () { const adapter = newBidder(spec) @@ -139,6 +159,40 @@ describe('yieldlabBidAdapter', function () { expect(request.url).to.include('schain=1.0,1!indirectseller.com,1,1,,,,!indirectseller2.com,2,1,,indirectseller2%20name%20with%20comma%20%2C%20and%20bang%20%21,,') }) + it('passes iab_content string to bid request', function () { + expect(request.url).to.include('iab_content=id%3Afoo_id%2Cepisode%3A99%2Ctitle%3Afoo_title%252Cbar_title%2Cseries%3Afoo_series%2Cseason%3As1%2Cartist%3Afoo%2520bar%2Cgenre%3Abaz%2Cisrc%3ACC-XXX-YY-NNNNN%2Curl%3Ahttp%253A%252F%252Ffoo_url.de%2Ccat%3Acat1%7Ccat2%252Cppp%7Ccat3%257C%257C%257C%252F%252F%2Ccontext%3A7%2Ckeywords%3Ak1%252C%7Ck2..%2Clive%3A0') + }) + + const siteConfig = { + 'ortb2': { + 'site': { + 'content': { + 'id': 'id_from_config' + } + } + } + } + + it('generates iab_content string from bidder params', function () { + config.setConfig(siteConfig); + const request = spec.buildRequests([REQUEST]) + expect(request.url).to.include('iab_content=id%3Afoo_id%2Cepisode%3A99%2Ctitle%3Afoo_title%252Cbar_title%2Cseries%3Afoo_series%2Cseason%3As1%2Cartist%3Afoo%2520bar%2Cgenre%3Abaz%2Cisrc%3ACC-XXX-YY-NNNNN%2Curl%3Ahttp%253A%252F%252Ffoo_url.de%2Ccat%3Acat1%7Ccat2%252Cppp%7Ccat3%257C%257C%257C%252F%252F%2Ccontext%3A7%2Ckeywords%3Ak1%252C%7Ck2..%2Clive%3A0') + config.resetConfig(); + }) + + it('generates iab_content string from first party data if not provided in bidder params', function () { + const requestWithoutIabContent = { + 'params': { + 'adslotId': '1111', + 'supplyId': '2222' + } + } + config.setConfig(siteConfig); + const request = spec.buildRequests([requestWithoutIabContent]) + expect(request.url).to.include('iab_content=id%3Aid_from_config') + config.resetConfig(); + }) + const refererRequest = spec.buildRequests(bidRequests, { refererInfo: { canonicalUrl: undefined, @@ -203,6 +257,11 @@ describe('yieldlabBidAdapter', function () { expect(result[0].ad).to.include('&consent=BN5lERiOMYEdiAKAWXEND1AAAAE6DABACMA') }) + it('should append iab_content to adtag', function () { + const result = spec.interpretResponse({body: [RESPONSE]}, {validBidRequests: [REQUEST], queryParams: REQPARAMS_IAB_CONTENT}) + expect(result[0].ad).to.include('&iab_content=id%3Afoo_id%2Cepisode%3A99%2Ctitle%3Afoo_title%252Cbar_title%2Cseries%3Afoo_series%2Cseason%3As1%2Cartist%3Afoo%2520bar%2Cgenre%3Abaz%2Cisrc%3ACC-XXX-YY-NNNNN%2Curl%3Ahttp%253A%252F%252Ffoo_url.de%2Ccat%3Acat1%7Ccat2%252Cppp%7Ccat3%257C%257C%257C%252F%252F%2Ccontext%3A7%2Ckeywords%3Ak1%252C%7Ck2..%2Clive%3A0') + }) + it('should get correct bid response when passing more than one size', function () { const REQUEST2 = Object.assign({}, REQUEST, { 'sizes': [ @@ -268,5 +327,10 @@ describe('yieldlabBidAdapter', function () { expect(result[0].ad).to.include('&pvid=43513f11-55a0-4a83-94e5-0ebc08f54a2c') expect(result[0].vastUrl).to.include('&pvid=43513f11-55a0-4a83-94e5-0ebc08f54a2c') }) + + it('should append iab_content to vastUrl', function () { + const result = spec.interpretResponse({body: [VIDEO_RESPONSE]}, {validBidRequests: [VIDEO_REQUEST], queryParams: REQPARAMS_IAB_CONTENT}) + expect(result[0].vastUrl).to.include('&iab_content=id%3Afoo_id%2Cepisode%3A99%2Ctitle%3Afoo_title%252Cbar_title%2Cseries%3Afoo_series%2Cseason%3As1%2Cartist%3Afoo%2520bar%2Cgenre%3Abaz%2Cisrc%3ACC-XXX-YY-NNNNN%2Curl%3Ahttp%253A%252F%252Ffoo_url.de%2Ccat%3Acat1%7Ccat2%252Cppp%7Ccat3%257C%257C%257C%252F%252F%2Ccontext%3A7%2Ckeywords%3Ak1%252C%7Ck2..%2Clive%3A0') + }) }) })