Skip to content

Commit

Permalink
RtbSape Bid Adapter: restore for Prebid 5.x (#7081)
Browse files Browse the repository at this point in the history
* RtbSape Bid Adapter: restore for Prebid 5.x

* RtbSape Bid Adapter: check adomain (#7081)

Co-authored-by: Dmitry Latyshev <d.latyshev@gmail.com>
  • Loading branch information
ne0n and Dmitry Latyshev authored Jul 6, 2021
1 parent f48fa6f commit 498e748
Show file tree
Hide file tree
Showing 2 changed files with 353 additions and 0 deletions.
144 changes: 144 additions & 0 deletions modules/rtbsapeBidAdapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import * as utils from '../src/utils.js';
import {registerBidder} from '../src/adapters/bidderFactory.js';
import {BANNER, VIDEO} from '../src/mediaTypes.js';
import {OUTSTREAM} from '../src/video.js';
import {Renderer} from '../src/Renderer.js';
import {triggerPixel} from '../src/utils.js';

const BIDDER_CODE = 'rtbsape';
const ENDPOINT = 'https://ssp-rtb.sape.ru/prebid';
const RENDERER_SRC = 'https://cdn-rtb.sape.ru/js/player.js';
const MATCH_SRC = 'https://www.acint.net/mc/?dp=141';

export const spec = {
code: BIDDER_CODE,
aliases: ['sape'],
supportedMediaTypes: [BANNER, VIDEO],

/**
* Determines whether or not the given bid request is valid.
*
* @param {BidRequest} bid The bid params to validate.
* @return boolean True if this is a valid bid, and false otherwise.
*/
isBidRequestValid: function (bid) {
return !!(bid && bid.mediaTypes && (bid.mediaTypes.banner || bid.mediaTypes.video) && bid.params && bid.params.placeId);
},

/**
* Make a server request from the list of BidRequests.
*
* @param {BidRequest[]} validBidRequests an array of bids
* @param bidderRequest
* @return ServerRequest Info describing the request to the server.
*/
buildRequests: function (validBidRequests, bidderRequest) {
let tz = (new Date()).getTimezoneOffset()
let padInt = (v) => (v < 10 ? '0' + v : '' + v);

return {
url: ENDPOINT,
method: 'POST',
data: {
auctionId: bidderRequest.auctionId,
requestId: bidderRequest.bidderRequestId,
bids: validBidRequests,
timezone: (tz > 0 ? '-' : '+') + padInt(Math.floor(Math.abs(tz) / 60)) + ':' + padInt(Math.abs(tz) % 60),
refererInfo: bidderRequest.refererInfo
},
}
},

/**
* Unpack the response from the server into a list of bids.
*
* @param {ServerResponse} serverResponse A successful response from the server.
* @param {{data: {bids: [{mediaTypes: {banner: boolean}}]}}} bidRequest Info describing the request to the server.
* @return {Bid[]} An array of bids which were nested inside the server.
*/
interpretResponse: function (serverResponse, bidRequest) {
if (!(serverResponse.body && Array.isArray(serverResponse.body.bids))) {
return [];
}

let bids = {};
bidRequest.data.bids.forEach(bid => bids[bid.bidId] = bid);

return serverResponse.body.bids
.filter(bid => typeof (bid.meta || {}).advertiserDomains !== 'undefined')
.map(bid => {
let requestBid = bids[bid.requestId];
let context = utils.deepAccess(requestBid, 'mediaTypes.video.context');

if (context === OUTSTREAM && (bid.vastUrl || bid.vastXml)) {
let renderer = Renderer.install({
id: bid.requestId,
url: RENDERER_SRC,
loaded: false
});

let muted = utils.deepAccess(requestBid, 'params.video.playerMuted');
if (typeof muted === 'undefined') {
muted = true;
}

bid.playerMuted = muted;
bid.renderer = renderer

renderer.setRender(setOutstreamRenderer);
}

return bid;
});
},

/**
* Register the user sync pixels which should be dropped after the auction.
*
* @param {SyncOptions} syncOptions Which user syncs are allowed?
* @return {UserSync[]} The user syncs which should be dropped.
*/
getUserSyncs: function (syncOptions) {
const sync = [];
if (syncOptions.iframeEnabled) {
sync.push({
type: 'iframe',
url: MATCH_SRC
});
}
return sync;
},

/**
* Register bidder specific code, which will execute if a bid from this bidder won the auction
* @param {Bid} bid The bid that won the auction
*/
onBidWon: function(bid) {
if (bid.nurl) {
triggerPixel(bid.nurl);
}
}
}

/**
* Initialize RtbSape outstream player
*
* @param bid
*/
function setOutstreamRenderer(bid) {
let props = {};
if (bid.vastUrl) {
props.url = bid.vastUrl;
}
if (bid.vastXml) {
props.xml = bid.vastXml;
}
bid.renderer.push(() => {
let player = window.sapeRtbPlayerHandler(bid.adUnitCode, bid.width, bid.height, bid.playerMuted, {singleton: true});
props.onComplete = () => player.destroy();
props.onError = () => player.destroy();
player.addSlot(props);
});
}

registerBidder(spec);
209 changes: 209 additions & 0 deletions test/spec/modules/rtbsapeBidAdapter_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
import {expect} from 'chai';
import {spec} from 'modules/rtbsapeBidAdapter.js';
import 'src/prebid.js';
import * as utils from 'src/utils.js';
import {executeRenderer, Renderer} from 'src/Renderer.js';

describe('rtbsapeBidAdapterTests', function () {
describe('isBidRequestValid', function () {
it('valid', function () {
expect(spec.isBidRequestValid({bidder: 'rtbsape', mediaTypes: {banner: true}, params: {placeId: 4321}})).to.equal(true);
expect(spec.isBidRequestValid({bidder: 'rtbsape', mediaTypes: {video: true}, params: {placeId: 4321}})).to.equal(true);
});

it('invalid', function () {
expect(spec.isBidRequestValid({bidder: 'rtbsape', mediaTypes: {banner: true}, params: {}})).to.equal(false);
expect(spec.isBidRequestValid({bidder: 'rtbsape', params: {placeId: 4321}})).to.equal(false);
});
});

it('buildRequests', function () {
let bidRequestData = [{
bidId: 'bid1234',
bidder: 'rtbsape',
params: {placeId: 4321},
sizes: [[240, 400]]
}];
let bidderRequest = {
auctionId: '2e208334-cafe-4c2c-b06b-f055ff876852',
bidderRequestId: '1392d0aa613366',
refererInfo: {}
};
let request = spec.buildRequests(bidRequestData, bidderRequest);
expect(request.data.auctionId).to.equal('2e208334-cafe-4c2c-b06b-f055ff876852');
expect(request.data.requestId).to.equal('1392d0aa613366');
expect(request.data.bids[0].bidId).to.equal('bid1234');
expect(request.data.timezone).to.not.equal(undefined);
});

describe('interpretResponse', function () {
it('banner', function () {
let serverResponse = {
body: {
bids: [{
requestId: 'bid1234',
cpm: 2.21,
currency: 'RUB',
width: 240,
height: 400,
netRevenue: true,
ad: 'Ad html',
meta: {
advertiserDomains: ['rtb.sape.ru']
}
}]
}
};
let bids = spec.interpretResponse(serverResponse, {data: {bids: [{mediaTypes: {banner: true}}]}});
expect(bids).to.have.lengthOf(1);
let bid = bids[0];
expect(bid.cpm).to.equal(2.21);
expect(bid.currency).to.equal('RUB');
expect(bid.width).to.equal(240);
expect(bid.height).to.equal(400);
expect(bid.netRevenue).to.equal(true);
expect(bid.requestId).to.equal('bid1234');
expect(bid.ad).to.equal('Ad html');
});

describe('video (outstream)', function () {
let bid;

before(() => {
let serverResponse = {
body: {
bids: [{
requestId: 'bid1234',
adUnitCode: 'ad-bid1234',
cpm: 3.32,
currency: 'RUB',
width: 600,
height: 340,
netRevenue: true,
vastUrl: 'https://cdn-rtb.sape.ru/vast/4321.xml',
meta: {
advertiserDomains: ['rtb.sape.ru'],
mediaType: 'video'
}
}]
}
};
let serverRequest = {
data: {
bids: [{
bidId: 'bid1234',
adUnitCode: 'ad-bid1234',
mediaTypes: {
video: {
context: 'outstream'
}
},
params: {
placeId: 4321,
video: {
playerMuted: false
}
}
}]
}
};
let bids = spec.interpretResponse(serverResponse, serverRequest);
expect(bids).to.have.lengthOf(1);
bid = bids[0];
});

it('should add renderer', () => {
expect(bid).to.have.own.property('renderer');
expect(bid.renderer).to.be.instanceof(Renderer);
expect(bid.renderer.url).to.equal('https://cdn-rtb.sape.ru/js/player.js');
expect(bid.playerMuted).to.equal(false);
});

it('should create player instance', () => {
let spy = false;

window.sapeRtbPlayerHandler = function (id, w, h, m) {
const player = {addSlot: () => [id, w, h, m]}
expect(spy).to.equal(false);
spy = sinon.spy(player, 'addSlot');
return player;
};

executeRenderer(bid.renderer, bid);
bid.renderer.callback();
expect(spy).to.not.equal(false);
expect(spy.called).to.be.true;

const spyCall = spy.getCall(0);
expect(spyCall.args[0].url).to.be.equal('https://cdn-rtb.sape.ru/vast/4321.xml');
expect(spyCall.returnValue[0]).to.be.equal('ad-bid1234');
expect(spyCall.returnValue[1]).to.be.equal(600);
expect(spyCall.returnValue[2]).to.be.equal(340);
expect(spyCall.returnValue[3]).to.be.equal(false);
});
});

it('skip adomain', function () {
let serverResponse = {
body: {
bids: [{
requestId: 'bid1234',
cpm: 2.21,
currency: 'RUB',
width: 240,
height: 400,
netRevenue: true,
ad: 'Ad html 1'
}, {
requestId: 'bid1235',
cpm: 2.23,
currency: 'RUB',
width: 300,
height: 250,
netRevenue: true,
ad: 'Ad html 2',
meta: {
advertiserDomains: ['rtb.sape.ru']
}
}]
}
};
let bids = spec.interpretResponse(serverResponse, {data: {bids: [{mediaTypes: {banner: true}}]}});
expect(bids).to.have.lengthOf(1);
let bid = bids[0];
expect(bid.cpm).to.equal(2.23);
expect(bid.currency).to.equal('RUB');
expect(bid.width).to.equal(300);
expect(bid.height).to.equal(250);
expect(bid.netRevenue).to.equal(true);
expect(bid.requestId).to.equal('bid1235');
expect(bid.ad).to.equal('Ad html 2');
});
});

it('getUserSyncs', function () {
const syncs = spec.getUserSyncs({iframeEnabled: true});
expect(syncs).to.be.an('array').that.to.have.lengthOf(1);
expect(syncs[0]).to.deep.equal({type: 'iframe', url: 'https://www.acint.net/mc/?dp=141'});
});

describe('onBidWon', function () {
beforeEach(function () {
sinon.stub(utils, 'triggerPixel');
});

afterEach(function () {
utils.triggerPixel.restore();
});

it('called once', function () {
spec.onBidWon({cpm: '2.21', nurl: 'https://ssp-rtb.sape.ru/track?event=win'});
expect(utils.triggerPixel.calledOnce).to.equal(true);
});

it('called false', function () {
spec.onBidWon({cpm: '2.21'});
expect(utils.triggerPixel.called).to.equal(false);
});
});
});

0 comments on commit 498e748

Please sign in to comment.