From 1075bf3e707d56670bab9c01eb3888f099b9dc29 Mon Sep 17 00:00:00 2001 From: Mario Ortas Date: Tue, 13 Jul 2021 20:47:16 +0200 Subject: [PATCH 01/22] added vidoomy bidder adapter with its tests and doc --- modules/vidoomyBidAdapter.js | 127 +++++++++++++ modules/vidoomyBidAdapter.md | 59 ++++++ test/spec/modules/vidoomyBidAdapter_spec.js | 190 ++++++++++++++++++++ 3 files changed, 376 insertions(+) create mode 100644 modules/vidoomyBidAdapter.js create mode 100644 modules/vidoomyBidAdapter.md create mode 100644 test/spec/modules/vidoomyBidAdapter_spec.js diff --git a/modules/vidoomyBidAdapter.js b/modules/vidoomyBidAdapter.js new file mode 100644 index 00000000000..9ac6079e812 --- /dev/null +++ b/modules/vidoomyBidAdapter.js @@ -0,0 +1,127 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {config} from '../src/config.js'; +import * as utils from '../src/utils.js'; +import { Renderer } from '../src/Renderer.js'; +import { OUTSTREAM } from '../src/video.js'; + +const ENDPOINT = `https://d.vidoomy.com/api/rtbserver/prebid/`; +const BIDDER_CODE = 'vidoomy'; +const isBidRequestValid = bid => { + if (!bid.params) { + utils.logError(BIDDER_CODE + ': bid.params should be non-empty'); + return false; + } + + if (!+bid.params.pid) { + utils.logError(BIDDER_CODE + ': bid.params.pid should be non-empty Number'); + return false; + } + + if (!+bid.params.id) { + utils.logError(BIDDER_CODE + ': bid.params.id should be non-empty Number'); + return false; + } + + return true; +}; + +const buildRequests = (validBidRequests, bidderRequest) => { + const serverRequests = validBidRequests.map(bid => { + const adType = Object.keys(bid.mediaTypes)[0]; + + let w, h; + + if (adType === VIDEO) { + [w, h] = bid.mediaTypes[adType].playerSize; + } + + if (adType === BANNER) { + [w, h] = bid.mediaTypes[adType].sizes[0]; + } + const videoContext = utils.deepAccess(bid, 'mediaTypes.video.context'); + + const queryParams = new URLSearchParams(); + queryParams.append('id', bid.params.id); + queryParams.append('adtype', adType); + queryParams.append('w', w); + queryParams.append('h', h); + queryParams.append('pos', parseInt(bid.params.position) || 1); + queryParams.append('ua', navigator.userAgent); + queryParams.append('l', navigator.language && navigator.language.indexOf('-') !== -1 ? navigator.language.split('-')[0] : ''); + queryParams.append('dt', /Mobi/.test(navigator.userAgent) ? 2 : 1); + queryParams.append('pid', bid.params.pid); + queryParams.append('dealId', bid.bidId); + queryParams.append('d', new URL(bidderRequest.refererInfo.referer).hostname); + queryParams.append('sp', encodeURIComponent(bidderRequest.refererInfo.referer)); + if (bidderRequest.gdprConsent) { + queryParams.append('gdpr', bidderRequest.gdprConsent.gdprApplies); + queryParams.append('gdprcs', bidderRequest.gdprConsent.consentString); + } + queryParams.append('usp', bidderRequest.uspConsent || ''); + queryParams.append('coppa', !!config.getConfig('coppa')); + + const url = `${ENDPOINT}?${queryParams.toString()}`; + return { + method: 'GET', + url: url, + data: {videoContext} + } + }); + return serverRequests; +}; + +const render = (bid) => { + bid.ad = bid.vastUrl; + var obj = { + vastTimeout: 5000, + maxAllowedVastTagRedirects: 3, + allowVpaid: true, + autoPlay: true, + preload: true, + mute: true, + } + window.outstreamPlayer(bid, bid.adUnitCode, obj); +} + +const interpretResponse = (serverResponse, bidRequest) => { + try { + let responseBody = serverResponse.body; + responseBody.requestId = responseBody.dealId; + if (responseBody.mediaType === 'video') { + responseBody.ad = responseBody.vastUrl; + const videoContext = bidRequest.data.videoContext; + + if (videoContext === OUTSTREAM) { + try { + const renderer = Renderer.install({ + id: bidRequest.bidId, + adunitcode: bidRequest.tagId, + loaded: false, + config: responseBody.mediaType, + url: responseBody.meta.rendererUrl + }); + renderer.setRender(render); + + responseBody.renderer = renderer; + } catch (e) { + responseBody.ad = responseBody.vastUrl; + } + } + } + + return [responseBody]; + } catch (e) { + return []; + } +}; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO], + isBidRequestValid, + buildRequests, + interpretResponse, +}; + +registerBidder(spec); diff --git a/modules/vidoomyBidAdapter.md b/modules/vidoomyBidAdapter.md new file mode 100644 index 00000000000..11e0ad40dbb --- /dev/null +++ b/modules/vidoomyBidAdapter.md @@ -0,0 +1,59 @@ +# Overview + +**Module Name:** Vidoomy Bid Adapter + +**Module Type:** Bidder Adapter + +**Maintainer:** support@vidoomy.com + +# Description + +Module to connect with Vidoomy, supporting banner and video + +# Test Parameters +For banner +```js +var adUnits = [ + { + code: 'test-ad', + mediaTypes: { + banner: { + sizes: [[300, 250]] // only first size will be accepted + } + }, + bids: [ + { + bidder: 'vidoomy', + params: { + id: '123123', + pid: '123123' + } + } + ] + } +]; +``` + +For video +```js +var adUnits = [ + { + code: 'test-ad', + mediaTypes: { + video: { + context: 'outstream', + playerSize: [300, 250] + } + }, + bids: [ + { + bidder: 'vidoomy', + params: { + id: '123123', + pid: '123123' + } + } + ] + } +]; +``` diff --git a/test/spec/modules/vidoomyBidAdapter_spec.js b/test/spec/modules/vidoomyBidAdapter_spec.js new file mode 100644 index 00000000000..b66ef448578 --- /dev/null +++ b/test/spec/modules/vidoomyBidAdapter_spec.js @@ -0,0 +1,190 @@ +import { expect } from 'chai'; +import { spec } from 'modules/vidoomyBidadapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; + +const ENDPOINT = `https://d.vidoomy.com/api/rtbserver/prebid/`; + +describe('vidoomyBidAdapter', function() { + const adapter = newBidder(spec); + + describe('isBidRequestValid', function () { + let bid; + beforeEach(() => { + bid = { + 'bidder': 'vidoomy', + 'params': { + pid: '123123', + id: '123123' + }, + 'adUnitCode': 'code', + 'sizes': [[300, 250]] + }; + }); + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when pid is empty', function () { + bid.params.pid = ''; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when id is empty', function () { + bid.params.id = ''; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when require params are not passed', function () { + let bid = Object.assign({}, bid); + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + let bidRequests = [ + { + 'bidder': 'vidoomy', + 'params': { + pid: '123123', + id: '123123' + }, + 'adUnitCode': 'code', + 'sizes': [[300, 250], [200, 100]] + }, + { + 'bidder': 'vidoomy', + 'params': { + pid: '456456', + id: '456456' + }, + 'adUnitCode': 'code2', + 'sizes': [[400, 225]] + } + ]; + + let bidderRequest = { + refererInfo: { + numIframes: 0, + reachedTop: true, + referer: 'http://example.com', + stack: ['http://example.com'] + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + + it('sends bid request to our endpoint via GET', function () { + expect(request[0].method).to.equal('GET'); + expect(request[1].method).to.equal('GET'); + }); + + it('attaches source and version to endpoint URL as query params', function () { + expect(request[0].url).to.include(ENDPOINT); + expect(request[1].url).to.include(ENDPOINT); + }); + + it('only accepts first width and height sizes', function () { + expect(request[0].url).to.include('w=300'); + expect(request[0].url).to.include('h=250'); + expect(request[0].url).to.not.include('w=200'); + expect(request[0].url).to.not.include('h=100'); + expect(request[1].url).to.include('w=400'); + expect(request[1].url).to.include('h=225'); + }); + + it('should send id and pid parameters', function () { + expect(request[0].url).to.include('id=123123'); + expect(request[0].url).to.include('pid=123123'); + expect(request[1].url).to.include('id=456456'); + expect(request[1].url).to.include('pid=456456'); + }); + }); + + describe('interpretResponse', function () { + const serverResponseVideo = { + body: { + 'vastUrl': 'https:\/\/vpaid.vidoomy.com\/demo-ad\/tag.xml', + 'mediaType': 'video', + 'requestId': '123123', + 'cpm': 3.265, + 'currency': 'USD', + 'width': 0, + 'height': 300, + 'creativeId': '123123', + 'dealId': '23cb20aa264b72', + 'netRevenue': true, + 'ttl': 60, + 'meta': { + 'mediaType': 'video', + 'rendererUrl': 'https:\/\/vpaid.vidoomy.com\/outstreamplayer\/bundle.js', + 'advertiserDomains': ['vidoomy.com'], + 'advertiserId': 123, + 'advertiserName': 'Vidoomy', + 'agencyId': null, + 'agencyName': null, + 'brandId': null, + 'brandName': null, + 'dchain': null, + 'networkId': null, + 'networkName': null, + 'primaryCatId': 'IAB3-1', + 'secondaryCatIds': null + } + } + } + + const serverResponseBanner = { + body: { + 'ad': '