Skip to content

Commit

Permalink
ADman Media Adapter: compatible with version 5 and support uid2 (#7383)
Browse files Browse the repository at this point in the history
* Add Adman bid adapter

* Add supportedMediaTypes property

* Update ADman Media bidder adapter

* Remove console.log

* Fix typo

* revert package-json.lock

* Delete package-lock.json

* back to original package-lock.json

* catch pbjs error

* catch pbjs error

* catch pbjs error

* log

* remove eu url

* remove eu url

* remove eu url

* remove eu url

* remove eu url

* Update admanBidAdapter.js

add consnet to sync url

* Update admanBidAdapter.js

fix import

* Update admanBidAdapter.js

lint fix

* Update admanBidAdapter.js

lint fix

* Update admanBidAdapter.js

check consent object data availability

* сompatible with prebid v5

Co-authored-by: minoru katogi <mkatogi@gmail.com>
Co-authored-by: minoru katogi <m_katogi@hotmail.com>
Co-authored-by: ADman Media <admanmedia@users.noreply.github.com>
Co-authored-by: SmartyAdman <adman@localhost.localdomain>
  • Loading branch information
5 people authored Sep 8, 2021
1 parent 906f908 commit 9f6daea
Show file tree
Hide file tree
Showing 2 changed files with 360 additions and 0 deletions.
173 changes: 173 additions & 0 deletions modules/admanBidAdapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
import {registerBidder} from '../src/adapters/bidderFactory.js';
import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js';
import * as utils from '../src/utils.js';

const BIDDER_CODE = 'adman';
const AD_URL = 'https://pub.admanmedia.com/?c=o&m=multi';
const URL_SYNC = 'https://pub.admanmedia.com/?c=o&m=sync';

function isBidResponseValid(bid) {
if (!bid.requestId || !bid.cpm || !bid.creativeId ||
!bid.ttl || !bid.currency) {
return false;
}
switch (bid['mediaType']) {
case BANNER:
return Boolean(bid.width && bid.height && bid.ad);
case VIDEO:
return Boolean(bid.vastUrl);
case NATIVE:
return Boolean(bid.native && bid.native.title && bid.native.image && bid.native.impressionTrackers);
default:
return false;
}
}

function getBidFloor(bid) {
if (!utils.isFn(bid.getFloor)) {
return utils.deepAccess(bid, 'params.bidfloor', 0);
}

try {
const bidFloor = bid.getFloor({
currency: 'USD',
mediaType: '*',
size: '*',
});
return bidFloor.floor;
} catch (_) {
return 0
}
}

function getUserId(eids, id, source, uidExt) {
if (id) {
var uid = { id };
if (uidExt) {
uid.ext = uidExt;
}
eids.push({
source,
uids: [ uid ]
});
}
}

export const spec = {
code: BIDDER_CODE,
supportedMediaTypes: [BANNER, VIDEO, NATIVE],

isBidRequestValid: (bid) => {
return Boolean(bid.bidId && bid.params && !isNaN(bid.params.placementId));
},

buildRequests: (validBidRequests = [], bidderRequest) => {
let winTop = window;
let location;
try {
location = new URL(bidderRequest.refererInfo.referer)
winTop = window.top;
} catch (e) {
location = winTop.location;
utils.logMessage(e);
};
let placements = [];
let request = {
'deviceWidth': winTop.screen.width,
'deviceHeight': winTop.screen.height,
'language': (navigator && navigator.language) ? navigator.language : '',
'secure': 1,
'host': location.host,
'page': location.pathname,
'placements': placements
};
request.language.indexOf('-') != -1 && (request.language = request.language.split('-')[0])
if (bidderRequest) {
if (bidderRequest.uspConsent) {
request.ccpa = bidderRequest.uspConsent;
}
if (bidderRequest.gdprConsent) {
request.gdpr = bidderRequest.gdprConsent
}
}
const len = validBidRequests.length;

for (let i = 0; i < len; i++) {
let bid = validBidRequests[i];
let traff = bid.params.traffic || BANNER
const placement = {
placementId: bid.params.placementId,
bidId: bid.bidId,
sizes: bid.mediaTypes && bid.mediaTypes[traff] && bid.mediaTypes[traff].sizes ? bid.mediaTypes[traff].sizes : [],
traffic: traff,
eids: [],
bidFloor: getBidFloor(bid)
}
if (bid.schain) {
placement.schain = bid.schain;
}
if (bid.userId) {
getUserId(placement.eids, bid.userId.uid2 && bid.userId.uid2.id, 'uidapi.com');
}
if (traff === VIDEO) {
placement.playerSize = bid.mediaTypes[VIDEO].playerSize;
placement.minduration = bid.mediaTypes[VIDEO].minduration;
placement.maxduration = bid.mediaTypes[VIDEO].maxduration;
placement.mimes = bid.mediaTypes[VIDEO].mimes;
placement.protocols = bid.mediaTypes[VIDEO].protocols;
placement.startdelay = bid.mediaTypes[VIDEO].startdelay;
placement.placement = bid.mediaTypes[VIDEO].placement;
placement.skip = bid.mediaTypes[VIDEO].skip;
placement.skipafter = bid.mediaTypes[VIDEO].skipafter;
placement.minbitrate = bid.mediaTypes[VIDEO].minbitrate;
placement.maxbitrate = bid.mediaTypes[VIDEO].maxbitrate;
placement.delivery = bid.mediaTypes[VIDEO].delivery;
placement.playbackmethod = bid.mediaTypes[VIDEO].playbackmethod;
placement.api = bid.mediaTypes[VIDEO].api;
placement.linearity = bid.mediaTypes[VIDEO].linearity;
}
placements.push(placement);
}
return {
method: 'POST',
url: AD_URL,
data: request
};
},

interpretResponse: (serverResponse) => {
let response = [];
serverResponse = serverResponse.body;
for (let i = 0; i < serverResponse.length; i++) {
let resItem = serverResponse[i];
if (isBidResponseValid(resItem)) {
const advertiserDomains = resItem.adomain && resItem.adomain.length ? resItem.adomain : [];
resItem.meta = { ...resItem.meta, advertiserDomains };

response.push(resItem);
}
}
return response;
},

getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => {
let syncUrl = URL_SYNC
if (gdprConsent && gdprConsent.consentString) {
if (typeof gdprConsent.gdprApplies === 'boolean') {
syncUrl += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`;
} else {
syncUrl += `&gdpr==0&gdpr_consent=${gdprConsent.consentString}`;
}
}
if (uspConsent && uspConsent.consentString) {
syncUrl += `&ccpa_consent=${uspConsent.consentString}`;
}
return [{
type: 'image',
url: syncUrl
}];
}

};

registerBidder(spec);
187 changes: 187 additions & 0 deletions test/spec/modules/admanBidAdapter_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
import {expect} from 'chai';
import {spec} from '../../../modules/admanBidAdapter.js';

describe('AdmanAdapter', function () {
let bid = {
bidId: '2dd581a2b6281d',
bidder: 'adman',
bidderRequestId: '145e1d6a7837c9',
params: {
placementId: 0
},
placementCode: 'placementid_0',
auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d2',
mediaTypes: {
banner: {
sizes: [[300, 250]]
}
},
transactionId: '3bb2f6da-87a6-4029-aeb0-bfe951372e62',
schain: {
ver: '1.0',
complete: 1,
nodes: [
{
asi: 'example.com',
sid: '0',
hp: 1,
rid: 'bidrequestid',
// name: 'alladsallthetime',
domain: 'example.com'
}
]
}
};
let bidderRequest = {
bidderCode: 'adman',
auctionId: 'fffffff-ffff-ffff-ffff-ffffffffffff',
bidderRequestId: 'ffffffffffffff',
start: 1472239426002,
auctionStart: 1472239426000,
timeout: 5000,
uspConsent: '1YN-',
refererInfo: {
referer: 'http://www.example.com',
reachedTop: true,
},
bids: [bid]
}

describe('isBidRequestValid', function () {
it('Should return true when placementId can be cast to a number', function () {
expect(spec.isBidRequestValid(bid)).to.be.true;
});
it('Should return false when placementId is not a number', function () {
bid.params.placementId = 'aaa';
expect(spec.isBidRequestValid(bid)).to.be.false;
});
});

describe('buildRequests', function () {
let serverRequest = spec.buildRequests([bid], bidderRequest);
it('Creates a ServerRequest object with method, URL and data', function () {
expect(serverRequest).to.exist;
expect(serverRequest.method).to.exist;
expect(serverRequest.url).to.exist;
expect(serverRequest.data).to.exist;
});
it('Returns POST method', function () {
expect(serverRequest.method).to.equal('POST');
});
it('Returns valid URL', function () {
expect(serverRequest.url).to.equal('https://pub.admanmedia.com/?c=o&m=multi');
});
it('Should contain ccpa', function() {
expect(serverRequest.data.ccpa).to.be.an('string')
})

it('Returns valid data if array of bids is valid', function () {
let data = serverRequest.data;
expect(data).to.be.an('object');
expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements', 'ccpa');
expect(data.deviceWidth).to.be.a('number');
expect(data.deviceHeight).to.be.a('number');
expect(data.language).to.be.a('string');
expect(data.secure).to.be.within(0, 1);
expect(data.host).to.be.a('string');
expect(data.page).to.be.a('string');
let placements = data['placements'];
for (let i = 0; i < placements.length; i++) {
let placement = placements[i];
expect(placement).to.have.all.keys('placementId', 'eids', 'bidId', 'traffic', 'sizes', 'schain', 'bidFloor');
expect(placement.schain).to.be.an('object')
expect(placement.placementId).to.be.a('number');
expect(placement.bidId).to.be.a('string');
expect(placement.traffic).to.be.a('string');
expect(placement.sizes).to.be.an('array');
expect(placement.bidFloor).to.be.an('number');
}
});
it('Returns empty data if no valid requests are passed', function () {
serverRequest = spec.buildRequests([]);
let data = serverRequest.data;
expect(data.placements).to.be.an('array').that.is.empty;
});
});

describe('buildRequests with user ids', function () {
bid.userId = {}
bid.userId.uid2 = { id: 'uid2id123' };
let serverRequest = spec.buildRequests([bid], bidderRequest);
it('Returns valid data if array of bids is valid', function () {
let data = serverRequest.data;
let placements = data['placements'];
expect(data).to.be.an('object');
for (let i = 0; i < placements.length; i++) {
let placement = placements[i];
expect(placement).to.have.property('eids')
expect(placement.eids).to.be.an('array')
expect(placement.eids.length).to.be.equal(1)
for (let index in placement.eids) {
let v = placement.eids[index];
expect(v).to.have.all.keys('source', 'uids')
expect(v.source).to.be.oneOf(['uidapi.com'])
expect(v.uids).to.be.an('array');
expect(v.uids.length).to.be.equal(1)
expect(v.uids[0]).to.have.property('id')
}
}
});
});

describe('interpretResponse', function () {
let resObject = {
body: [ {
requestId: '123',
mediaType: 'banner',
cpm: 0.3,
width: 320,
height: 50,
ad: '<h1>Hello ad</h1>',
ttl: 1000,
creativeId: '123asd',
netRevenue: true,
currency: 'USD',
meta: {
advertiserDomains: ['google.com'],
advertiserId: 1234
}
} ]
};
let serverResponses = spec.interpretResponse(resObject);
it('Returns an array of valid server responses if response object is valid', function () {
expect(serverResponses).to.be.an('array').that.is.not.empty;
for (let i = 0; i < serverResponses.length; i++) {
let dataItem = serverResponses[i];
expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId',
'netRevenue', 'currency', 'mediaType', 'meta');
expect(dataItem.requestId).to.be.a('string');
expect(dataItem.cpm).to.be.a('number');
expect(dataItem.width).to.be.a('number');
expect(dataItem.height).to.be.a('number');
expect(dataItem.ad).to.be.a('string');
expect(dataItem.ttl).to.be.a('number');
expect(dataItem.creativeId).to.be.a('string');
expect(dataItem.netRevenue).to.be.a('boolean');
expect(dataItem.currency).to.be.a('string');
expect(dataItem.mediaType).to.be.a('string');
expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains');
}
it('Returns an empty array if invalid response is passed', function () {
serverResponses = spec.interpretResponse('invalid_response');
expect(serverResponses).to.be.an('array').that.is.empty;
});
});
});

describe('getUserSyncs', function () {
let userSync = spec.getUserSyncs();
it('Returns valid URL and type', function () {
expect(userSync).to.be.an('array').with.lengthOf(1);
expect(userSync[0].type).to.exist;
expect(userSync[0].url).to.exist;
expect(userSync[0].type).to.be.equal('image');
expect(userSync[0].url).to.be.equal('https://pub.admanmedia.com/?c=o&m=sync');
});
});
});

0 comments on commit 9f6daea

Please sign in to comment.