Skip to content

Commit

Permalink
Lm_Kiviads Bid Adapter : initial adapter release (#10415)
Browse files Browse the repository at this point in the history
* new adapter - KiviAds

* add gpp, change tests, use bidderRequest instead of config.getConfig()

* Update kiviadsBidAdapter_spec.js

* add new bid adapter

* Update lm_kiviadsBidAdapter: change placement -> pid according to server-side parameters

* fix aliases

---------

Co-authored-by: Patrick McCann <[email protected]>
Co-authored-by: Chucky-choo <[email protected]>
  • Loading branch information
3 people authored Oct 3, 2023
1 parent 2905b4c commit a2fc26a
Show file tree
Hide file tree
Showing 3 changed files with 715 additions and 0 deletions.
206 changes: 206 additions & 0 deletions modules/lm_kiviadsBidAdapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
import {config} from '../src/config.js';
import {BANNER, VIDEO} from '../src/mediaTypes.js';
import {registerBidder} from '../src/adapters/bidderFactory.js';
import {getAdUnitSizes, parseSizesInput, isFn, deepAccess, getBidIdParameter, logError, isArray} from '../src/utils.js';

const CUR = 'USD';
const BIDDER_CODE = 'lm_kiviads';
const ENDPOINT = 'https://pbjs.kiviads.live';

/**
* 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.
*/
function isBidRequestValid(req) {
if (req && typeof req.params !== 'object') {
logError('Params is not defined or is incorrect in the bidder settings');
return false;
}

if (!getBidIdParameter('env', req.params) || !getBidIdParameter('pid', req.params)) {
logError('Env or pid is not present in bidder params');
return false;
}

if (deepAccess(req, 'mediaTypes.video') && !isArray(deepAccess(req, 'mediaTypes.video.playerSize'))) {
logError('mediaTypes.video.playerSize is required for video');
return false;
}

return true;
}

/**
* Make a server request from the list of BidRequests.
*
* @param {validBidRequest?pbjs_debug=trues[]} - an array of bids
* @return ServerRequest Info describing the request to the server.
*/
function buildRequests(validBidRequests, bidderRequest) {
const {refererInfo = {}, gdprConsent = {}, uspConsent} = bidderRequest;
const requests = validBidRequests.map(req => {
const request = {};
request.bidId = req.bidId;
request.banner = deepAccess(req, 'mediaTypes.banner');
request.auctionId = req.ortb2?.source?.tid;
request.transactionId = req.ortb2Imp?.ext?.tid;
request.sizes = parseSizesInput(getAdUnitSizes(req));
request.schain = req.schain;
request.location = {
page: refererInfo.page,
location: refererInfo.location,
domain: refererInfo.domain,
whost: window.location.host,
ref: refererInfo.ref,
isAmp: refererInfo.isAmp
};
request.device = {
ua: navigator.userAgent,
lang: navigator.language
};
request.env = {
env: req.params.env,
pid: req.params.pid
};
request.ortb2 = req.ortb2;
request.ortb2Imp = req.ortb2Imp;
request.tz = new Date().getTimezoneOffset();
request.ext = req.params.ext;
request.bc = req.bidRequestsCount;
request.floor = getBidFloor(req);

if (req.userIdAsEids && req.userIdAsEids.length !== 0) {
request.userEids = req.userIdAsEids;
} else {
request.userEids = [];
}
if (gdprConsent.gdprApplies) {
request.gdprApplies = Number(gdprConsent.gdprApplies);
request.consentString = gdprConsent.consentString;
} else {
request.gdprApplies = 0;
request.consentString = '';
}
if (uspConsent) {
request.usPrivacy = uspConsent;
} else {
request.usPrivacy = '';
}
if (config.getConfig('coppa')) {
request.coppa = 1;
} else {
request.coppa = 0;
}

const video = deepAccess(req, 'mediaTypes.video');
if (video) {
request.sizes = parseSizesInput(deepAccess(req, 'mediaTypes.video.playerSize'));
request.video = video;
}

return request;
});

return {
method: 'POST',
url: ENDPOINT + '/bid',
data: JSON.stringify(requests),
withCredentials: true,
bidderRequest,
options: {
contentType: 'application/json',
}
};
}

/**
* Unpack the response from the server into a list of bids.
*
* @param {ServerResponse} serverResponse A successful response from the server.
* @return {Bid[]} An array of bids which were nested inside the server.
*/
function interpretResponse(serverResponse, {bidderRequest}) {
const response = [];
if (!isArray(deepAccess(serverResponse, 'body.data'))) {
return response;
}

serverResponse.body.data.forEach(serverBid => {
const bid = {
requestId: bidderRequest.bidId,
dealId: bidderRequest.dealId || null,
...serverBid
};
response.push(bid);
});

return response;
}

/**
* Register the user sync pixels which should be dropped after the auction.
*
* @param {SyncOptions} syncOptions Which user syncs are allowed?
* @param {ServerResponse[]} serverResponses List of server's responses.
* @return {UserSync[]} The user syncs which should be dropped.
*/
function getUserSyncs(syncOptions, serverResponses, gdprConsent = {}, uspConsent = '') {
const syncs = [];
const pixels = deepAccess(serverResponses, '0.body.data.0.ext.pixels');

if ((syncOptions.iframeEnabled || syncOptions.pixelEnabled) && isArray(pixels) && pixels.length !== 0) {
const gdprFlag = `&gdpr=${gdprConsent.gdprApplies ? 1 : 0}`;
const gdprString = `&gdpr_consent=${encodeURIComponent((gdprConsent.consentString || ''))}`;
const usPrivacy = `us_privacy=${encodeURIComponent(uspConsent)}`;

pixels.forEach(pixel => {
const [type, url] = pixel;
const sync = {type, url: `${url}&${usPrivacy}${gdprFlag}${gdprString}`};
if (type === 'iframe' && syncOptions.iframeEnabled) {
syncs.push(sync)
} else if (type === 'image' && syncOptions.pixelEnabled) {
syncs.push(sync)
}
});
}

return syncs;
}

/**
* Get valid floor value from getFloor fuction.
*
* @param {Object} bid Current bid request.
* @return {null|Number} Returns floor value when bid.getFloor is function and returns valid floor object with USD currency, otherwise returns null.
*/
export function getBidFloor(bid) {
if (!isFn(bid.getFloor)) {
return null;
}

let floor = bid.getFloor({
currency: CUR,
mediaType: '*',
size: '*'
});

if (typeof floor === 'object' && !isNaN(floor.floor) && floor.currency === CUR) {
return floor.floor;
}

return null;
}

export const spec = {
code: BIDDER_CODE,
aliases: ['kivi'],
supportedMediaTypes: [BANNER, VIDEO],
isBidRequestValid,
buildRequests,
interpretResponse,
getUserSyncs
}

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

```
Module Name: lm_kiviads Bidder Adapter
Module Type: lm_kiviads Bidder Adapter
Maintainer: [email protected]
```

# Description

Module that connects to kiviads.com demand sources

# Test Parameters
```
var adUnits = [
{
code: 'test-banner',
mediaTypes: {
banner: {
sizes: [[300, 250]],
}
},
bids: [
{
bidder: 'lm_kiviads',
params: {
env: 'lm_kiviads',
pid: '40',
ext: {}
}
}
]
},
{
code: 'test-video',
sizes: [ [ 640, 480 ] ],
mediaTypes: {
video: {
playerSize: [640, 480],
context: 'instream',
skipppable: true
}
},
bids: [{
bidder: 'lm_kiviads',
params: {
env: 'lm_kiviads',
pid: '40',
ext: {}
}
}]
}
];
```
Loading

0 comments on commit a2fc26a

Please sign in to comment.