From 571265acf3a931ad7a1d1d5331440426963f272f Mon Sep 17 00:00:00 2001 From: Rok Susnik Date: Thu, 26 Nov 2020 16:15:08 +0100 Subject: [PATCH 1/4] Add the Zemanta adapter --- modules/zemantaBidAdapter.js | 252 ++++++++++++ modules/zemantaBidAdapter.md | 95 +++++ test/spec/modules/zemantaBidAdapter_spec.js | 434 ++++++++++++++++++++ 3 files changed, 781 insertions(+) create mode 100644 modules/zemantaBidAdapter.js create mode 100644 modules/zemantaBidAdapter.md create mode 100644 test/spec/modules/zemantaBidAdapter_spec.js diff --git a/modules/zemantaBidAdapter.js b/modules/zemantaBidAdapter.js new file mode 100644 index 00000000000..d7102823618 --- /dev/null +++ b/modules/zemantaBidAdapter.js @@ -0,0 +1,252 @@ +// jshint esversion: 6, es3: false, node: true +'use strict'; + +import { + registerBidder +} from '../src/adapters/bidderFactory.js'; +import { NATIVE, BANNER } from '../src/mediaTypes.js'; +import * as utils from '../src/utils.js'; +import { ajax } from '../src/ajax.js'; +import { config } from '../src/config.js'; + +const BIDDER_CODE = 'zemanta'; +const CURRENCY = 'USD'; +const NATIVE_ASSET_IDS = { 0: 'title', 2: 'icon', 3: 'image', 5: 'sponsoredBy', 4: 'body', 1: 'cta' }; +const NATIVE_PARAMS = { + title: { id: 0, name: 'title' }, + icon: { id: 2, type: 1, name: 'img' }, + image: { id: 3, type: 3, name: 'img' }, + sponsoredBy: { id: 5, name: 'data', type: 1 }, + body: { id: 4, name: 'data', type: 2 }, + cta: { id: 1, type: 12, name: 'data' } +}; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [ NATIVE, BANNER ], + isBidRequestValid: (bid) => { + return !!(bid.nativeParams || bid.sizes); + }, + buildRequests: (validBidRequests, bidderRequest) => { + const page = bidderRequest.refererInfo.referer; + const ua = navigator.userAgent; + const test = setOnAny(validBidRequests, 'params.test'); + const publisher = setOnAny(validBidRequests, 'params.publisher'); + const cur = CURRENCY; + const endpointUrl = config.getConfig('zemanta.bidderUrl'); + const timeout = bidderRequest.timeout; + + const imps = validBidRequests.map((bid, id) => { + bid.netRevenue = 'net'; + const imp = { + id: id + 1 + '' + } + + if (bid.params.tagid) { + imp.tagid = bid.params.tagid + } + + if (bid.nativeParams) { + imp.native = { + request: JSON.stringify({ + assets: getNativeAssets(bid) + }) + } + } else { + imp.banner = { + format: transformSizes(bid.sizes) + } + } + + return imp; + }); + + const request = { + id: bidderRequest.auctionId, + site: { page, publisher }, + device: { ua }, + source: { fd: 1 }, + cur: [cur], + tmax: timeout, + imp: imps + }; + + if (test) { + request.is_debug = !!test; + request.test = 1; + } + + if (utils.deepAccess(bidderRequest, 'gdprConsent.gdprApplies')) { + utils.deepSetValue(request, 'user.ext.consent', bidderRequest.gdprConsent.consentString) + utils.deepSetValue(request, 'regs.ext.gdpr', bidderRequest.gdprConsent.gdprApplies & 1) + } + if (bidderRequest.uspConsent) { + utils.deepSetValue(request, 'regs.ext.us_privacy', bidderRequest.uspConsent) + } + if (config.getConfig('coppa') === true) { + utils.deepSetValue(request, 'regs.coppa', config.getConfig('coppa') & 1) + } + + return { + method: 'POST', + url: endpointUrl, + data: JSON.stringify(request), + bids: validBidRequests + }; + }, + interpretResponse: (serverResponse, { bids }) => { + if (!serverResponse.body) { + return []; + } + const { seatbid, cur } = serverResponse.body; + + const bidResponses = flatten(seatbid.map(seat => seat.bid)).reduce((result, bid) => { + result[bid.impid - 1] = bid; + return result; + }, []); + + return bids.map((bid, id) => { + const bidResponse = bidResponses[id]; + if (bidResponse) { + const type = bid.nativeParams ? NATIVE : BANNER; + const bidObject = { + requestId: bid.bidId, + cpm: bidResponse.price, + creativeId: bidResponse.crid, + ttl: 360, + netRevenue: bid.netRevenue === 'net', + currency: cur, + mediaType: type, + nurl: bidResponse.nurl, + }; + if (type === NATIVE) { + bidObject.native = parseNative(bidResponse); + } else { + bidObject.ad = bidResponse.adm; + bidObject.width = bidResponse.w; + bidObject.height = bidResponse.h; + } + return bidObject; + } + }).filter(Boolean); + }, + getUserSyncs: (syncOptions) => { + const syncs = []; + const syncUrl = config.getConfig('zemanta.usersyncUrl'); + if (syncOptions.pixelEnabled) { + syncs.push({ + type: 'image', + url: syncUrl + }); + } + return syncs; + }, + onBidWon: (bid) => { + ajax(utils.replaceAuctionPrice(bid.nurl, bid.originalCpm)) + } +}; + +registerBidder(spec); + +function parseNative(bid) { + const { assets, link, eventtrackers } = JSON.parse(bid.adm); + const result = { + clickUrl: link.url, + clickTrackers: link.clicktrackers || undefined + }; + assets.forEach(asset => { + const kind = NATIVE_ASSET_IDS[asset.id]; + const content = kind && asset[NATIVE_PARAMS[kind].name]; + if (content) { + result[kind] = content.text || content.value || { url: content.url, width: content.w, height: content.h }; + } + }); + if (eventtrackers) { + result.impressionTrackers = []; + eventtrackers.forEach(tracker => { + if (tracker.event !== 1) return; + switch (tracker.method) { + case 1: // img + result.impressionTrackers.push(tracker.url); + break; + case 2: // js + result.javascriptTrackers = ``; + break; + } + }); + } + return result; +} + +function setOnAny(collection, key) { + for (let i = 0, result; i < collection.length; i++) { + result = utils.deepAccess(collection[i], key); + if (result) { + return result; + } + } +} + +function flatten(arr) { + return [].concat(...arr); +} + +function getNativeAssets(bid) { + return utils._map(bid.nativeParams, (bidParams, key) => { + const props = NATIVE_PARAMS[key]; + const asset = { + required: bidParams.required & 1, + }; + if (props) { + asset.id = props.id; + let wmin, hmin, w, h; + let aRatios = bidParams.aspect_ratios; + + if (aRatios && aRatios[0]) { + aRatios = aRatios[0]; + wmin = aRatios.min_width || 0; + hmin = aRatios.ratio_height * wmin / aRatios.ratio_width | 0; + } + + if (bidParams.sizes) { + const sizes = flatten(bidParams.sizes); + w = sizes[0]; + h = sizes[1]; + } + + asset[props.name] = { + len: bidParams.len, + type: props.type, + wmin, + hmin, + w, + h + }; + + return asset; + } + }).filter(Boolean); +} + +/* Turn bid request sizes into ut-compatible format */ +function transformSizes(requestSizes) { + if (!utils.isArray(requestSizes)) { + return []; + } + + if (requestSizes.length === 2 && !utils.isArray(requestSizes[0])) { + return [{ + w: parseInt(requestSizes[0], 10), + h: parseInt(requestSizes[1], 10) + }]; + } else if (utils.isArray(requestSizes[0])) { + return requestSizes.map(item => + ({ + w: parseInt(item[0], 10), + h: parseInt(item[1], 10) + }) + ); + } + + return []; +} diff --git a/modules/zemantaBidAdapter.md b/modules/zemantaBidAdapter.md new file mode 100644 index 00000000000..b86230a3a81 --- /dev/null +++ b/modules/zemantaBidAdapter.md @@ -0,0 +1,95 @@ +# Overview + +``` +Module Name: Zemanta Adapter +Module Type: Bidder Adapter +Maintainer: prog-ops-team@outbrain.com +``` + +# Description + +Module that connects to zemanta bidder to fetch bids. +Both native and display formats are supported but not at the same time. Using OpenRTB standard. + +# Configuration + +## Bidder and usersync URLs + +The Zemanta adapter does not work without setting the correct bidder and usersync URLs. +You will receive the URLs when contacting us. + +``` +pbjs.setConfig({ + zemanta: { + bidderUrl: 'https://bidder-url.com', + usersyncUrl: 'https://usersync-url.com' + } +}); +``` + + +# Test Native Parameters +``` + var adUnits = [ + code: '/19968336/prebid_native_example_1', + mediaTypes: { + native: { + image: { + required: false, + sizes: [100, 50] + }, + title: { + required: false, + len: 140 + }, + sponsoredBy: { + required: false + }, + clickUrl: { + required: false + }, + body: { + required: false + }, + icon: { + required: false, + sizes: [50, 50] + } + } + }, + bids: [{ + bidder: 'zemanta', + params: { + publisher: { + id: '2706', // required + name: 'Publishers Name', + domain: 'publisher.com' + }, + tagid: 'tag-id' + } + }] + ]; +``` + +# Test Display Parameters +``` + var adUnits = [ + code: '/19968336/prebid_display_example_1', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [{ + bidder: 'zemanta', + params: { + publisher: { + id: '2706', // required + name: 'Publishers Name', + domain: 'publisher.com' + } + tagid: 'tag-id' + }, + }] + ]; +``` diff --git a/test/spec/modules/zemantaBidAdapter_spec.js b/test/spec/modules/zemantaBidAdapter_spec.js new file mode 100644 index 00000000000..aa0e5ad0ed3 --- /dev/null +++ b/test/spec/modules/zemantaBidAdapter_spec.js @@ -0,0 +1,434 @@ +import {expect} from 'chai'; +import {spec} from 'modules/zemantaBidAdapter.js'; +import {config} from 'src/config.js'; +import {server} from 'test/mocks/xhr'; + +describe('Zemanta Adapter', function () { + describe('Bid request and response', function () { + const commonBidRequest = { + bidder: 'zemanta', + params: { + publisher: { + id: 'publisher-id' + }, + }, + bidId: '2d6815a92ba1ba', + auctionId: '12043683-3254-4f74-8934-f941b085579e', + } + const nativeBidRequestParams = { + nativeParams: { + image: { + required: true, + sizes: [ + 120, + 100 + ], + sendId: true + }, + title: { + required: true, + sendId: true + }, + sponsoredBy: { + required: false + } + }, + } + + const displayBidRequestParams = { + sizes: [ + [ + 300, + 250 + ] + ] + } + + describe('isBidRequestValid', function () { + it('should fail when bid is invalid', function () { + const bid = { + bidder: 'zemanta' + } + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) + it('should work when bid contains native params', function () { + const bid = { + bidder: 'zemanta', + ...nativeBidRequestParams, + } + expect(spec.isBidRequestValid(bid)).to.equal(true) + }) + it('should work when bid contains sizes', function () { + const bid = { + bidder: 'zemanta', + ...displayBidRequestParams, + } + expect(spec.isBidRequestValid(bid)).to.equal(true) + }) + }) + + describe('buildRequests', function () { + before(() => { + config.setConfig({ + zemanta: { + bidderUrl: 'https://bidder-url.com', + } + } + ) + }) + after(() => { + config.resetConfig() + }) + + const commonBidderRequest = { + refererInfo: { + referer: 'https://example.com/' + } + } + + it('should build native request', function () { + const bidRequest = { + ...commonBidRequest, + ...nativeBidRequestParams, + } + const expectedNativeAssets = { + assets: [ + { + required: 1, + id: 3, + img: { + type: 3, + w: 120, + h: 100 + } + }, + { + required: 1, + id: 0, + title: {} + }, + { + required: 0, + id: 5, + data: { + type: 1 + } + } + ] + } + const expectedData = { + site: { + page: 'https://example.com/', + publisher: { + id: 'publisher-id' + } + }, + device: { + ua: navigator.userAgent + }, + source: { + fd: 1 + }, + cur: [ + 'USD' + ], + imp: [ + { + id: '1', + native: { + request: JSON.stringify(expectedNativeAssets) + } + } + ] + } + const res = spec.buildRequests([bidRequest], commonBidderRequest) + expect(res.url).to.equal('https://bidder-url.com') + expect(res.data).to.deep.equal(JSON.stringify(expectedData)) + }); + + it('should build display request', function () { + const bidRequest = { + ...commonBidRequest, + ...displayBidRequestParams, + } + const expectedData = { + site: { + page: 'https://example.com/', + publisher: { + id: 'publisher-id' + } + }, + device: { + ua: navigator.userAgent + }, + source: { + fd: 1 + }, + cur: [ + 'USD' + ], + imp: [ + { + id: '1', + banner: { + format: [ + { + w: 300, + h: 250 + } + ] + } + } + ] + } + const res = spec.buildRequests([bidRequest], commonBidderRequest) + expect(res.url).to.equal('https://bidder-url.com') + expect(res.data).to.deep.equal(JSON.stringify(expectedData)) + }) + + it('should pass optional tagid in request', function () { + const bidRequest = { + ...commonBidRequest, + ...nativeBidRequestParams, + } + bidRequest.params.tagid = 'test-tag' + + const res = spec.buildRequests([bidRequest], commonBidderRequest) + const resData = JSON.parse(res.data) + expect(resData.imp[0].tagid).to.equal('test-tag') + }); + + it('should pass bidder timeout', function () { + const bidRequest = { + ...commonBidRequest, + ...nativeBidRequestParams, + } + const bidderRequest = { + ...commonBidderRequest, + timeout: 500 + } + const res = spec.buildRequests([bidRequest], bidderRequest) + const resData = JSON.parse(res.data) + expect(resData.tmax).to.equal(500) + }); + + it('should pass GDPR consent', function () { + const bidRequest = { + ...commonBidRequest, + ...nativeBidRequestParams, + } + const bidderRequest = { + ...commonBidderRequest, + gdprConsent: { + gdprApplies: true, + consentString: 'consentString', + } + } + const res = spec.buildRequests([bidRequest], bidderRequest) + const resData = JSON.parse(res.data) + expect(resData.user.ext.consent).to.equal('consentString') + expect(resData.regs.ext.gdpr).to.equal(1) + }); + + it('should pass us privacy consent', function () { + const bidRequest = { + ...commonBidRequest, + ...nativeBidRequestParams, + } + const bidderRequest = { + ...commonBidderRequest, + uspConsent: 'consentString' + } + const res = spec.buildRequests([bidRequest], bidderRequest) + const resData = JSON.parse(res.data) + expect(resData.regs.ext.us_privacy).to.equal('consentString') + }); + + it('should pass coppa consent', function () { + const bidRequest = { + ...commonBidRequest, + ...nativeBidRequestParams, + } + config.setConfig({coppa: true}) + + const res = spec.buildRequests([bidRequest], commonBidderRequest) + const resData = JSON.parse(res.data) + expect(resData.regs.coppa).to.equal(1) + + config.resetConfig() + }); + }) + + describe('interpretResponse', function () { + it('should return empty array if no valid bids', function () { + const res = spec.interpretResponse({}, []) + expect(res).to.be.an('array').that.is.empty + }); + + it('should interpret native response', function () { + const serverResponse = { + body: { + id: '0a73e68c-9967-4391-b01b-dda2d9fc54e4', + seatbid: [ + { + bid: [ + { + id: '82822cf5-259c-11eb-8a52-f29e5275aa57', + impid: '1', + price: 1.1, + nurl: 'http://example.com/win/${AUCTION_PRICE}', + adm: '{"ver":"1.2","assets":[{"id":3,"required":1,"img":{"url":"http://example.com/img/url","w":120,"h":100}},{"id":0,"required":1,"title":{"text":"Test title"}},{"id":5,"data":{"value":"Test sponsor"}}],"link":{"url":"http://example.com/click/url"},"eventtrackers":[{"event":1,"method":1,"url":"http://example.com/impression"}]}', + adomain: [ + 'example.co' + ], + cid: '3487171', + crid: '28023739', + cat: [ + 'IAB10-2' + ] + } + ], + seat: 'acc-5537' + } + ], + bidid: '82822cf5-259c-11eb-8a52-b48e7518c657', + cur: 'USD' + }, + } + const request = { + bids: [ + { + ...commonBidRequest, + ...nativeBidRequestParams, + } + ] + } + const expectedRes = [ + { + requestId: request.bids[0].bidId, + cpm: 1.1, + creativeId: '28023739', + ttl: 360, + netRevenue: false, + currency: 'USD', + mediaType: 'native', + nurl: 'http://example.com/win/${AUCTION_PRICE}', + native: { + clickTrackers: undefined, + clickUrl: 'http://example.com/click/url', + image: { + url: 'http://example.com/img/url', + width: 120, + height: 100 + }, + title: 'Test title', + sponsoredBy: 'Test sponsor', + impressionTrackers: [ + 'http://example.com/impression', + ] + } + } + ] + + const res = spec.interpretResponse(serverResponse, request) + expect(res).to.deep.equal(expectedRes) + }); + + it('should interpret display response', function () { + const serverResponse = { + body: { + id: '6b2eedc8-8ff5-46ef-adcf-e701b508943e', + seatbid: [ + { + bid: [ + { + id: 'd90fe7fa-28d7-11eb-8ce4-462a842a7cf9', + impid: '1', + price: 1.1, + nurl: 'http://example.com/win/${AUCTION_PRICE}', + adm: '
ad
', + adomain: [ + 'example.com' + ], + cid: '3865084', + crid: '29998660', + cat: [ + 'IAB10-2' + ], + w: 300, + h: 250 + } + ], + seat: 'acc-6536' + } + ], + bidid: 'd90fe7fa-28d7-11eb-8ce4-13d94bfa26f9', + cur: 'USD' + } + } + const request = { + bids: [ + { + ...commonBidRequest, + ...displayBidRequestParams + } + ] + } + const expectedRes = [ + { + requestId: request.bids[0].bidId, + cpm: 1.1, + creativeId: '29998660', + ttl: 360, + netRevenue: false, + currency: 'USD', + mediaType: 'banner', + nurl: 'http://example.com/win/${AUCTION_PRICE}', + ad: '
ad
', + width: 300, + height: 250 + } + ] + + const res = spec.interpretResponse(serverResponse, request) + expect(res).to.deep.equal(expectedRes) + }); + }) + }) + + describe('getUserSyncs', function () { + before(() => { + config.setConfig({ + zemanta: { + usersyncUrl: 'https://usersync-url.com', + } + } + ) + }) + after(() => { + config.resetConfig() + }) + + it('should return user sync if pixel enabled', function () { + const ret = spec.getUserSyncs({pixelEnabled: true}) + expect(ret).to.deep.equal([{type: 'image', url: 'https://usersync-url.com'}]) + }) + + it('should not return user sync if pixel disabled', function () { + const ret = spec.getUserSyncs({pixelEnabled: false}) + expect(ret).to.be.an('array').that.is.empty + }) + }) + + describe('onBidWon', function () { + it('should make an ajax call with the original cpm', function () { + const bid = { + nurl: 'http://example.com/win/${AUCTION_PRICE}', + cpm: 2.1, + originalCpm: 1.1, + } + spec.onBidWon(bid) + expect(server.requests[0].url).to.equals('http://example.com/win/1.1') + }); + }) +}) From 8e5f1fdef7c08ee85fee89c208a7b9143460d621 Mon Sep 17 00:00:00 2001 From: Rok Susnik Date: Mon, 7 Dec 2020 15:16:44 +0100 Subject: [PATCH 2/4] fix documentation --- modules/zemantaBidAdapter.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/zemantaBidAdapter.md b/modules/zemantaBidAdapter.md index b86230a3a81..36b26b60922 100644 --- a/modules/zemantaBidAdapter.md +++ b/modules/zemantaBidAdapter.md @@ -87,7 +87,7 @@ pbjs.setConfig({ id: '2706', // required name: 'Publishers Name', domain: 'publisher.com' - } + }, tagid: 'tag-id' }, }] From 4e25d76f94c97240024c022f852709a6c0e237e9 Mon Sep 17 00:00:00 2001 From: Rok Susnik Date: Mon, 7 Dec 2020 15:17:35 +0100 Subject: [PATCH 3/4] improve validation --- modules/zemantaBidAdapter.js | 8 ++- test/spec/modules/zemantaBidAdapter_spec.js | 59 +++++++++++++++++++-- 2 files changed, 62 insertions(+), 5 deletions(-) diff --git a/modules/zemantaBidAdapter.js b/modules/zemantaBidAdapter.js index d7102823618..aa7a24985e0 100644 --- a/modules/zemantaBidAdapter.js +++ b/modules/zemantaBidAdapter.js @@ -25,7 +25,11 @@ export const spec = { code: BIDDER_CODE, supportedMediaTypes: [ NATIVE, BANNER ], isBidRequestValid: (bid) => { - return !!(bid.nativeParams || bid.sizes); + return ( + !!config.getConfig('zemanta.bidderUrl') && + !!utils.deepAccess(bid, 'params.publisher.id') && + !!(bid.nativeParams || bid.sizes) + ); }, buildRequests: (validBidRequests, bidderRequest) => { const page = bidderRequest.refererInfo.referer; @@ -133,7 +137,7 @@ export const spec = { getUserSyncs: (syncOptions) => { const syncs = []; const syncUrl = config.getConfig('zemanta.usersyncUrl'); - if (syncOptions.pixelEnabled) { + if (syncOptions.pixelEnabled && syncUrl) { syncs.push({ type: 'image', url: syncUrl diff --git a/test/spec/modules/zemantaBidAdapter_spec.js b/test/spec/modules/zemantaBidAdapter_spec.js index aa0e5ad0ed3..ab685890620 100644 --- a/test/spec/modules/zemantaBidAdapter_spec.js +++ b/test/spec/modules/zemantaBidAdapter_spec.js @@ -45,26 +45,73 @@ describe('Zemanta Adapter', function () { } describe('isBidRequestValid', function () { + before(() => { + config.setConfig({ + zemanta: { + bidderUrl: 'https://bidder-url.com', + } + } + ) + }) + after(() => { + config.resetConfig() + }) + it('should fail when bid is invalid', function () { const bid = { - bidder: 'zemanta' + bidder: 'zemanta', + params: { + publisher: { + id: 'publisher-id', + } + }, } expect(spec.isBidRequestValid(bid)).to.equal(false) }) - it('should work when bid contains native params', function () { + it('should succeed when bid contains native params', function () { const bid = { bidder: 'zemanta', + params: { + publisher: { + id: 'publisher-id', + } + }, ...nativeBidRequestParams, } expect(spec.isBidRequestValid(bid)).to.equal(true) }) - it('should work when bid contains sizes', function () { + it('should succeed when bid contains sizes', function () { const bid = { bidder: 'zemanta', + params: { + publisher: { + id: 'publisher-id', + } + }, ...displayBidRequestParams, } expect(spec.isBidRequestValid(bid)).to.equal(true) }) + it('should fail if publisher id is not set', function () { + const bid = { + bidder: 'zemanta', + ...nativeBidRequestParams, + } + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) + it('should fail if bidder url is not set', function () { + const bid = { + bidder: 'zemanta', + params: { + publisher: { + id: 'publisher-id', + } + }, + ...nativeBidRequestParams, + } + config.resetConfig() + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) }) describe('buildRequests', function () { @@ -418,6 +465,12 @@ describe('Zemanta Adapter', function () { const ret = spec.getUserSyncs({pixelEnabled: false}) expect(ret).to.be.an('array').that.is.empty }) + + it('should not return user sync if url is not set', function () { + config.resetConfig() + const ret = spec.getUserSyncs({pixelEnabled: true}) + expect(ret).to.be.an('array').that.is.empty + }) }) describe('onBidWon', function () { From 3d838b9679ccb2ec64090a40ce618ab3260b2a00 Mon Sep 17 00:00:00 2001 From: Rok Susnik Date: Thu, 10 Dec 2020 16:23:16 +0100 Subject: [PATCH 4/4] add test bidder urls to Zemanta docs --- modules/zemantaBidAdapter.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/modules/zemantaBidAdapter.md b/modules/zemantaBidAdapter.md index 36b26b60922..d991b67d429 100644 --- a/modules/zemantaBidAdapter.md +++ b/modules/zemantaBidAdapter.md @@ -69,6 +69,12 @@ pbjs.setConfig({ } }] ]; + + pbjs.setConfig({ + zemanta: { + bidderUrl: 'https://prebidtest.zemanta.com/api/bidder/prebidtest/bid/' + } + }); ``` # Test Display Parameters @@ -92,4 +98,10 @@ pbjs.setConfig({ }, }] ]; + + pbjs.setConfig({ + zemanta: { + bidderUrl: 'https://prebidtest.zemanta.com/api/bidder/prebidtest/bid/' + } + }); ```