Skip to content

Commit

Permalink
Alkimi Bid Adapter: add new bid adapter (#8326)
Browse files Browse the repository at this point in the history
* Alkimi bid adapter

* Alkimi bid adapter

* Alkimi bid adapter

* alkimi adapter

* onBidWon change

* sign utils

* auction ID as bid request ID

* unit test fixes

Co-authored-by: Alexander Bogdanov <akascheev@asteriosoft.com>
  • Loading branch information
pro-nsk and Alexander Bogdanov authored May 9, 2022
1 parent a47650b commit 028843d
Show file tree
Hide file tree
Showing 3 changed files with 312 additions and 0 deletions.
119 changes: 119 additions & 0 deletions modules/alkimiBidAdapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { registerBidder } from '../src/adapters/bidderFactory.js';
import { deepClone, deepAccess } from '../src/utils.js';
import { ajax } from '../src/ajax.js';
import { VIDEO } from '../src/mediaTypes.js';
import { config } from '../src/config.js';

const BIDDER_CODE = 'alkimi';
export const ENDPOINT = 'https://exchange.alkimi-onboarding.com/bid?prebid=true';

export const spec = {
code: BIDDER_CODE,
supportedMediaTypes: ['banner', 'video'],

isBidRequestValid: function (bid) {
return !!(bid.params && bid.params.bidFloor && bid.params.token);
},

buildRequests: function (validBidRequests, bidderRequest) {
let bids = [];
let bidIds = [];
validBidRequests.forEach(bidRequest => {
let sizes = prepareSizes(bidRequest.sizes)

bids.push({
token: bidRequest.params.token,
pos: bidRequest.params.pos,
bidFloor: bidRequest.params.bidFloor,
width: sizes[0].width,
height: sizes[0].height,
impMediaType: getFormatType(bidRequest)
})
bidIds.push(bidRequest.bidId)
})

const alkimiConfig = config.getConfig('alkimi');

let payload = {
requestId: bidderRequest.auctionId,
signRequest: { bids, randomUUID: alkimiConfig && alkimiConfig.randomUUID },
bidIds,
referer: bidderRequest.refererInfo.referer,
signature: alkimiConfig && alkimiConfig.signature
}

const options = {
contentType: 'application/json',
customHeaders: {
'Rtb-Direct': true
}
}

return {
method: 'POST',
url: ENDPOINT,
data: payload,
options
};
},

interpretResponse: function (serverResponse, request) {
const serverBody = serverResponse.body;
if (!serverBody || typeof serverBody !== 'object') {
return [];
}

const { prebidResponse } = serverBody;
if (!prebidResponse || typeof prebidResponse !== 'object') {
return [];
}

let bids = [];
prebidResponse.forEach(bidResponse => {
let bid = deepClone(bidResponse);
bid.cpm = parseFloat(bidResponse.cpm);

// banner or video
if (VIDEO === bid.mediaType) {
bid.vastXml = bid.ad;
}

bid.meta = {};
bid.meta.advertiserDomains = bid.adomain || [];

bids.push(bid);
})

return bids;
},

onBidWon: function (bid) {
let winUrl;
if (bid.winUrl || bid.vastUrl) {
winUrl = bid.winUrl ? bid.winUrl : bid.vastUrl;
winUrl = winUrl.replace(/\$\{AUCTION_PRICE\}/, bid.cpm);
} else if (bid.ad) {
let trackImg = bid.ad.match(/(?!^)<img src=".+dsp-win.+">/);
bid.ad = bid.ad.replace(trackImg[0], '');
winUrl = trackImg[0].split('"')[1];
winUrl = winUrl.replace(/\$%7BAUCTION_PRICE%7D/, bid.cpm);
} else {
return false;
}

ajax(winUrl, null);
return true;
}
}

function prepareSizes(sizes) {
return sizes && sizes.map(size => ({ width: size[0], height: size[1] }));
}

const getFormatType = bidRequest => {
if (deepAccess(bidRequest, 'mediaTypes.banner')) return 'Banner'
if (deepAccess(bidRequest, 'mediaTypes.video')) return 'Video'
if (deepAccess(bidRequest, 'mediaTypes.audio')) return 'Audio'
}

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

```
Module Name: Alkimi Bidder Adapter
Module Type: Bidder Adapter
Maintainer: abogdanov@asteriosoft.com
```

# Description

Connects to Alkimi Bidder for bids.
Alkimi bid adapter supports Banner and Video ads.

# Test Parameters
```
const adUnits = [
{
bids: [
{
bidder: 'alkimi',
params: {
bidFloor: 0.1,
token: '?????????????????????', // Publisher Token provided by Alkimi
}
}
]
}
];
```
164 changes: 164 additions & 0 deletions test/spec/modules/alkimiBidAdapter_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import { expect } from 'chai'
import { ENDPOINT, spec } from 'modules/alkimiBidAdapter.js'
import { newBidder } from 'src/adapters/bidderFactory.js'

const REQUEST = {
'bidId': '456',
'bidder': 'alkimi',
'sizes': [[300, 250]],
'mediaTypes': {
'banner': {
'sizes': [[300, 250]]
}
},
'params': {
bidFloor: 0.1,
token: 'e64782a4-8e68-4c38-965b-80ccf115d46f',
pos: 7
}
}

const BIDDER_BANNER_RESPONSE = {
'prebidResponse': [{
'ad': '<div>test</div>',
'requestId': 'e64782a4-8e68-4c38-965b-80ccf115d46d',
'cpm': 900.5,
'currency': 'USD',
'width': 640,
'height': 480,
'ttl': 300,
'creativeId': 1,
'netRevenue': true,
'winUrl': 'http://test.com',
'mediaType': 'banner',
'adomain': ['test.com']
}]
}

const BIDDER_VIDEO_RESPONSE = {
'prebidResponse': [{
'ad': '<xml>vast</xml>',
'requestId': 'e64782a4-8e68-4c38-965b-80ccf115d46z',
'cpm': 800.4,
'currency': 'USD',
'width': 1024,
'height': 768,
'ttl': 200,
'creativeId': 2,
'netRevenue': true,
'winUrl': 'http://test.com',
'mediaType': 'video',
'adomain': ['test.com']
}]
}

const BIDDER_NO_BID_RESPONSE = ''

describe('alkimiBidAdapter', function () {
const adapter = newBidder(spec)

describe('inherited functions', function () {
it('exists and is a function', function () {
expect(adapter.callBids).to.exist.and.to.be.a('function')
})
})

describe('isBidRequestValid', function () {
it('should return true when required params found', function () {
expect(spec.isBidRequestValid(REQUEST)).to.equal(true)
})

it('should return false when required params are not passed', function () {
let bid = Object.assign({}, REQUEST)
delete bid.params.token
expect(spec.isBidRequestValid(bid)).to.equal(false)

bid = Object.assign({}, REQUEST)
delete bid.params.bidFloor
expect(spec.isBidRequestValid(bid)).to.equal(false)

bid = Object.assign({}, REQUEST)
delete bid.params
expect(spec.isBidRequestValid(bid)).to.equal(false)
})
})

describe('buildRequests', function () {
let bidRequests = [REQUEST]
const bidderRequest = spec.buildRequests(bidRequests, {
auctionId: '123',
refererInfo: {
referer: 'http://test.com/path.html'
}
})

it('sends bid request to ENDPOINT via POST', function () {
expect(bidderRequest.method).to.equal('POST')
expect(bidderRequest.data.requestId).to.equal('123')
expect(bidderRequest.data.referer).to.equal('http://test.com/path.html')
expect(bidderRequest.data.signRequest.bids).to.deep.contains({ token: 'e64782a4-8e68-4c38-965b-80ccf115d46f', pos: 7, bidFloor: 0.1, width: 300, height: 250, impMediaType: 'Banner' })
expect(bidderRequest.data.signRequest.randomUUID).to.equal(undefined)
expect(bidderRequest.data.bidIds).to.deep.contains('456')
expect(bidderRequest.data.signature).to.equal(undefined)
expect(bidderRequest.options.customHeaders).to.deep.equal({ 'Rtb-Direct': true })
expect(bidderRequest.options.contentType).to.equal('application/json')
expect(bidderRequest.url).to.equal(ENDPOINT)
})
})

describe('interpretResponse', function () {
it('handles banner request : should get correct bid response', function () {
const result = spec.interpretResponse({ body: BIDDER_BANNER_RESPONSE }, {})

expect(result[0]).to.have.property('ad').equal('<div>test</div>')
expect(result[0]).to.have.property('requestId').equal('e64782a4-8e68-4c38-965b-80ccf115d46d')
expect(result[0]).to.have.property('cpm').equal(900.5)
expect(result[0]).to.have.property('currency').equal('USD')
expect(result[0]).to.have.property('width').equal(640)
expect(result[0]).to.have.property('height').equal(480)
expect(result[0]).to.have.property('ttl').equal(300)
expect(result[0]).to.have.property('creativeId').equal(1)
expect(result[0]).to.have.property('netRevenue').equal(true)
expect(result[0]).to.have.property('winUrl').equal('http://test.com')
expect(result[0]).to.have.property('mediaType').equal('banner')
expect(result[0].meta).to.exist.property('advertiserDomains')
expect(result[0].meta).to.have.property('advertiserDomains').lengthOf(1)
})

it('handles video request : should get correct bid response', function () {
const result = spec.interpretResponse({ body: BIDDER_VIDEO_RESPONSE }, {})

expect(result[0]).to.have.property('ad').equal('<xml>vast</xml>')
expect(result[0]).to.have.property('requestId').equal('e64782a4-8e68-4c38-965b-80ccf115d46z')
expect(result[0]).to.have.property('cpm').equal(800.4)
expect(result[0]).to.have.property('currency').equal('USD')
expect(result[0]).to.have.property('width').equal(1024)
expect(result[0]).to.have.property('height').equal(768)
expect(result[0]).to.have.property('ttl').equal(200)
expect(result[0]).to.have.property('creativeId').equal(2)
expect(result[0]).to.have.property('netRevenue').equal(true)
expect(result[0]).to.have.property('winUrl').equal('http://test.com')
expect(result[0]).to.have.property('mediaType').equal('video')
expect(result[0]).to.have.property('vastXml').equal('<xml>vast</xml>')
expect(result[0].meta).to.exist.property('advertiserDomains')
expect(result[0].meta).to.have.property('advertiserDomains').lengthOf(1)
})

it('handles no bid response : should get empty array', function () {
let result = spec.interpretResponse({ body: undefined }, {})
expect(result).to.deep.equal([])

result = spec.interpretResponse({ body: BIDDER_NO_BID_RESPONSE }, {})
expect(result).to.deep.equal([])
})
})

describe('onBidWon', function () {
it('handles banner win: should get true', function () {
const win = BIDDER_BANNER_RESPONSE.prebidResponse[0]
const bidWonResult = spec.onBidWon(win)

expect(bidWonResult).to.equal(true)
})
})
})

0 comments on commit 028843d

Please sign in to comment.