Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vidoomy Bid Adapter: add new bid adapter #7178

Merged
merged 24 commits into from
Aug 3, 2021
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 132 additions & 0 deletions modules/vidoomyBidAdapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
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;
}
ncolletti marked this conversation as resolved.
Show resolved Hide resolved

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 => {
let adType = BANNER;

let w, h;
if (bid.mediaTypes && bid.mediaTypes[BANNER] && bid.mediaTypes[BANNER].sizes) {
[w, h] = bid.mediaTypes[BANNER].sizes[0];
adType = BANNER;
} else if (bid.mediaTypes && bid.mediaTypes[VIDEO] && bid.mediaTypes[VIDEO].playerSize) {
[w, h] = bid.mediaTypes[VIDEO].playerSize;
adType = VIDEO;
}

const host = bidderRequest.refererInfo.referer.split('#')[0].replace(/^(https\:\/\/|http\:\/\/)|(\/)$/g, '').split('/')[0];
const hostname = host.split(':')[0];
ncolletti marked this conversation as resolved.
Show resolved Hide resolved

const videoContext = utils.deepAccess(bid, 'mediaTypes.video.context');

const queryParams = [];
queryParams.push(['id', bid.params.id]);
queryParams.push(['adtype', adType]);
queryParams.push(['w', w]);
queryParams.push(['h', h]);
queryParams.push(['pos', parseInt(bid.params.position) || 1]);
queryParams.push(['ua', navigator.userAgent]);
queryParams.push(['l', navigator.language && navigator.language.indexOf('-') !== -1 ? navigator.language.split('-')[0] : '']);
queryParams.push(['dt', /Mobi/.test(navigator.userAgent) ? 2 : 1]);
queryParams.push(['pid', bid.params.pid]);
queryParams.push(['dealId', bid.bidId]);
queryParams.push(['d', hostname]);
queryParams.push(['sp', encodeURIComponent(bidderRequest.refererInfo.referer)]);
if (bidderRequest.gdprConsent) {
queryParams.push(['gdpr', bidderRequest.gdprConsent.gdprApplies]);
queryParams.push(['gdprcs', bidderRequest.gdprConsent.consentString]);
}
queryParams.push(['usp', bidderRequest.uspConsent || '']);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given you are using a GET and building this data into the URL request it would help to avoid unnecessary characters by not including 'usp' or 'l' if they are empty strings

queryParams.push(['coppa', !!config.getConfig('coppa')]);
ncolletti marked this conversation as resolved.
Show resolved Hide resolved

const rawQueryParams = queryParams.map(qp => qp.join('=')).join('&');

const url = `${ENDPOINT}?${rawQueryParams}`;
return {
method: 'GET',
url: url,
data: {videoContext}
}
});
return serverRequests;
};

const render = (bid) => {
bid.ad = bid.vastUrl;
var obj = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May be helpful in the future to name this object.

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;
ncolletti marked this conversation as resolved.
Show resolved Hide resolved
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;
}
ncolletti marked this conversation as resolved.
Show resolved Hide resolved
}
}

return [responseBody];
ncolletti marked this conversation as resolved.
Show resolved Hide resolved
} catch (e) {
return [];
}
};

export const spec = {
code: BIDDER_CODE,
supportedMediaTypes: [BANNER, VIDEO],
isBidRequestValid,
buildRequests,
interpretResponse,
ncolletti marked this conversation as resolved.
Show resolved Hide resolved
};

registerBidder(spec);
59 changes: 59 additions & 0 deletions modules/vidoomyBidAdapter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Overview

**Module Name:** Vidoomy Bid Adapter

**Module Type:** Bidder Adapter

**Maintainer:** [email protected]

# 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'
}
}
]
}
];
```
200 changes: 200 additions & 0 deletions test/spec/modules/vidoomyBidAdapter_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
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',
'mediaTypes': {
'banner': {
'context': 'outstream',
'sizes': [[300, 250], [200, 100]]
}
},
},
{
'bidder': 'vidoomy',
'params': {
pid: '456456',
id: '456456'
},
'mediaTypes': {
'video': {
'context': 'outstream',
'playerSize': [400, 225],
}
},
'adUnitCode': 'code2',
}
];

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': '<iframe src=\'https:\/\/vidoomy.com\/render\/ad.html\' width=\'300\' height=\'250\' frameborder=\'0\' scrolling=\'no\' marginheight=\'0\' marginwidth=\'0\' topmargin=\'0\' leftmargin=\'0\'><\/iframe>',
'mediaType': 'banner',
'requestId': '123123',
'cpm': 4.94,
'currency': 'USD',
'width': 250,
'height': 300,
'creativeId': '123123',
'dealId': '230aa095cea76b',
ncolletti marked this conversation as resolved.
Show resolved Hide resolved
'netRevenue': true,
'ttl': 60,
'meta': {
'mediaType': 'banner',
'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
}
}
}

it('should get the correct bid response for outstream video, with renderer, an url in ad, and a new requestId equal to dealId', function () {
const bidRequest = {
data: {
videoContext: 'outstream'
}
}

let result = spec.interpretResponse(serverResponseVideo, bidRequest);

expect(result[0].renderer).to.not.be.undefined;
expect(result[0].ad).to.equal(serverResponseVideo.body.vastUrl);
expect(result[0].requestId).to.equal(serverResponseVideo.body.dealId);
});

it('should get the correct bid response for banner with a new requestId equal to dealId', function () {
const bidRequest = {};
let result = spec.interpretResponse(serverResponseBanner, bidRequest);

expect(result[0].requestId).to.equal(serverResponseBanner.body.dealId);
});
});
});