From 3ac6d48cd55857dd4cc93698e8d171ea7b12f6c2 Mon Sep 17 00:00:00 2001 From: Taras Saveliev Date: Mon, 28 Feb 2022 11:43:31 +0300 Subject: [PATCH 1/2] add Yandex Bidder Adapter --- modules/yandexBidAdapter.js | 103 +++++++++++++ modules/yandexBidAdapter.md | 40 +++++ test/spec/modules/yandexBidAdapter_spec.js | 164 +++++++++++++++++++++ 3 files changed, 307 insertions(+) create mode 100644 modules/yandexBidAdapter.js create mode 100644 modules/yandexBidAdapter.md create mode 100644 test/spec/modules/yandexBidAdapter_spec.js diff --git a/modules/yandexBidAdapter.js b/modules/yandexBidAdapter.js new file mode 100644 index 00000000000..4cc25965431 --- /dev/null +++ b/modules/yandexBidAdapter.js @@ -0,0 +1,103 @@ +import {parseUrl, formatQS, deepAccess} from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; + +const BIDDER_CODE = 'yandex'; +const BIDDER_URL = 'https://bs-metadsp.yandex.ru/metadsp'; +const DEFAULT_TTL = 180; +const SSP_ID = 10500; + +export const spec = { + code: BIDDER_CODE, + aliases: ['ya'], // short code + + isBidRequestValid: function(bid) { + return !!(bid.params && bid.params.pageId && bid.params.impId); + }, + + buildRequests: function(validBidRequests, bidderRequest) { + const gdprApplies = deepAccess(bidderRequest, 'gdprConsent.gdprApplies'); + const consentString = deepAccess(bidderRequest, 'gdprConsent.consentString'); + + let referrer = ''; + if (bidderRequest && bidderRequest.refererInfo) { + const url = parseUrl(bidderRequest.refererInfo.referer); + referrer = url.hostname; + } + + return validBidRequests.map((bidRequest) => { + const { params } = bidRequest; + const { pageId, impId, targetRef, withCredentials = true } = params; + + const queryParams = { + 'imp-id': impId, + 'target-ref': targetRef || referrer, + 'ssp-id': SSP_ID, + }; + if (gdprApplies !== undefined) { + queryParams['gdpr'] = 1; + queryParams['tcf-consent'] = consentString; + } + const imp = { + id: impId, + }; + + const bannerParams = deepAccess(bidRequest, 'mediaTypes.banner'); + if (bannerParams) { + const [ w, h ] = bannerParams.sizes[0]; + imp.banner = { + w, + h, + }; + } + + const queryParamsString = formatQS(queryParams); + return { + method: 'POST', + url: BIDDER_URL + `/${pageId}?${queryParamsString}`, + data: { + id: bidRequest.bidId, + imp: [imp], + site: { + page: referrer, + }, + }, + options: { + withCredentials, + }, + bidRequest, + } + }); + }, + + interpretResponse: function(serverResponse, {bidRequest}) { + let response = serverResponse.body; + if (!response.seatbid) { + return []; + } + + const { cur, seatbid } = serverResponse.body; + const rtbBids = seatbid + .map(seatbid => seatbid.bid) + .reduce((a, b) => a.concat(b), []); + + return rtbBids.map(rtbBid => { + let prBid = { + requestId: bidRequest.bidId, + cpm: rtbBid.price, + currency: cur || 'USD', + width: rtbBid.w, + height: rtbBid.h, + creativeId: rtbBid.adid, + + netRevenue: true, + ttl: DEFAULT_TTL, + }; + + prBid.ad = rtbBid.adm; + + return prBid; + }); + }, +} + +registerBidder(spec); diff --git a/modules/yandexBidAdapter.md b/modules/yandexBidAdapter.md new file mode 100644 index 00000000000..7a51d7bc5fb --- /dev/null +++ b/modules/yandexBidAdapter.md @@ -0,0 +1,40 @@ +# Overview + +``` +Module Name: Yandex Bidder Adapter +Module Type: Bidder Adapter +Maintainer: prebid@yandex-team.com +``` + +# Description + +Yandex Bidder Adapter for Prebid.js. + +# Parameters + +| Name | Scope | Description | Example | Type | +|---------------|----------|-------------------------|-----------|-----------| +| `pageId` | required | Page ID | `123` | `Integer` | +| `impId` | required | Block ID | `1` | `Integer` | + +# Test Parameters + +``` +var adUnits = [{ + code: 'banner-1', + mediaTypes: { + banner: { + sizes: [[240, 400]], + } + }, + bids: [{ + { + bidder: 'yandex', + params: { + pageId: 346580, + impId: 143, + }, + } + }] +}]; +``` diff --git a/test/spec/modules/yandexBidAdapter_spec.js b/test/spec/modules/yandexBidAdapter_spec.js new file mode 100644 index 00000000000..c4bbcf3f4fd --- /dev/null +++ b/test/spec/modules/yandexBidAdapter_spec.js @@ -0,0 +1,164 @@ +import {assert, expect} from 'chai'; +import {spec} from 'modules/yandexBidAdapter.js'; +import {parseUrl} from 'src/utils.js'; +import {BANNER} from '../../../src/mediaTypes'; + +describe('Yandex adapter', function () { + function getBidConfig() { + return { + bidder: 'yandex', + params: { + pageId: 123, + impId: 1, + }, + }; + } + + function getBidRequest() { + return { + ...getBidConfig(), + bidId: 'bidid-1', + adUnitCode: 'adUnit-123', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600] + ], + }, + }, + }; + } + + describe('isBidRequestValid', function () { + it('should return true when required params found', function () { + const bid = getBidConfig(); + assert(spec.isBidRequestValid(bid)); + }); + + it('should return false when required params not found', function () { + expect(spec.isBidRequestValid({})).to.be.false; + }); + + it('should return false when required params.pageId are not passed', function () { + const bid = getBidConfig(); + delete bid.params.pageId; + + expect(spec.isBidRequestValid(bid)).to.be.false + }); + + it('should return false when required params.impId are not passed', function () { + const bid = getBidConfig(); + delete bid.params.impId; + + expect(spec.isBidRequestValid(bid)).to.be.false + }); + }); + + describe('buildRequests', function () { + const refererUrl = 'https://yandex.ru/secure-ads'; + + const gdprConsent = { + gdprApplies: 1, + consentString: 'concent-string', + apiVersion: 1, + }; + + const bidderRequest = { + refererInfo: { + referer: refererUrl + }, + gdprConsent + }; + + it('creates a valid banner request', function () { + const bannerRequest = getBidRequest(); + bannerRequest.getFloor = () => ({ + currency: 'USD', + // floor: 0.5 + }); + + const requests = spec.buildRequests([bannerRequest], bidderRequest); + + expect(requests).to.have.lengthOf(1); + const request = requests[0]; + + expect(request).to.exist; + const { method, url, data } = request; + + expect(method).to.equal('POST'); + + const parsedRequestUrl = parseUrl(url); + const { search: query } = parsedRequestUrl + + expect(parsedRequestUrl.hostname).to.equal('bs-metadsp.yandex.ru'); + expect(parsedRequestUrl.pathname).to.equal('/metadsp/123'); + + expect(query['imp-id']).to.equal('1'); + expect(query['target-ref']).to.equal('yandex.ru'); + expect(query['ssp-id']).to.equal('10500'); + + expect(query['gdpr']).to.equal('1'); + expect(query['tcf-consent']).to.equal('concent-string'); + + expect(request.data).to.exist; + expect(data.site).to.not.equal(null); + expect(data.site.page).to.equal('yandex.ru'); + + // expect(data.device).to.not.equal(null); + // expect(data.device.w).to.equal(window.innerWidth); + // expect(data.device.h).to.equal(window.innerHeight); + + expect(data.imp).to.have.lengthOf(1); + expect(data.imp[0].banner).to.not.equal(null); + expect(data.imp[0].banner.w).to.equal(300); + expect(data.imp[0].banner.h).to.equal(250); + }); + }); + + describe('response handler', function () { + const bannerRequest = getBidRequest(); + + const bannerResponse = { + body: { + seatbid: [{ + bid: [ + { + impid: '1', + price: 0.3, + crid: 321, + adm: '', + w: 300, + h: 250, + adomain: [ + 'example.com' + ], + adid: 'yabs.123=', + } + ] + }], + cur: 'USD', + }, + }; + + it('handles banner responses', function () { + bannerRequest.bidRequest = { + mediaType: BANNER, + bidId: 'bidid-1', + }; + const result = spec.interpretResponse(bannerResponse, bannerRequest); + + expect(result).to.have.lengthOf(1); + expect(result[0]).to.exist; + + const rtbBid = result[0]; + expect(rtbBid.width).to.equal(300); + expect(rtbBid.height).to.equal(250); + expect(rtbBid.cpm).to.be.within(0.1, 0.5); + expect(rtbBid.ad).to.equal(''); + expect(rtbBid.currency).to.equal('USD'); + expect(rtbBid.netRevenue).to.equal(true); + expect(rtbBid.ttl).to.equal(180); + }); + }); +}); From 6e316a8defb53ab40f87696c30a91b6c3b0abb34 Mon Sep 17 00:00:00 2001 From: Taras Saveliev Date: Fri, 1 Apr 2022 14:03:21 +0300 Subject: [PATCH 2/2] add support for adomains --- modules/yandexBidAdapter.js | 4 ++++ test/spec/modules/yandexBidAdapter_spec.js | 2 ++ 2 files changed, 6 insertions(+) diff --git a/modules/yandexBidAdapter.js b/modules/yandexBidAdapter.js index 4cc25965431..e20f71bc08d 100644 --- a/modules/yandexBidAdapter.js +++ b/modules/yandexBidAdapter.js @@ -91,6 +91,10 @@ export const spec = { netRevenue: true, ttl: DEFAULT_TTL, + + meta: { + advertiserDomains: rtbBid.adomain && rtbBid.adomain.length > 0 ? rtbBid.adomain : [], + } }; prBid.ad = rtbBid.adm; diff --git a/test/spec/modules/yandexBidAdapter_spec.js b/test/spec/modules/yandexBidAdapter_spec.js index c4bbcf3f4fd..833f883fb7c 100644 --- a/test/spec/modules/yandexBidAdapter_spec.js +++ b/test/spec/modules/yandexBidAdapter_spec.js @@ -159,6 +159,8 @@ describe('Yandex adapter', function () { expect(rtbBid.currency).to.equal('USD'); expect(rtbBid.netRevenue).to.equal(true); expect(rtbBid.ttl).to.equal(180); + + expect(rtbBid.meta.advertiserDomains).to.deep.equal(['example.com']); }); }); });