Skip to content

Commit

Permalink
Kobler Bid Adapter: add new bid adapter (#6479)
Browse files Browse the repository at this point in the history
* Implemented Kobler bidder adapter.

* Added missing '' to dealId example in parameters table.

* Added information on supporting the Floors module.

* Implemented tests for isBidRequestValid.

* Implemented tests for inherited functions.

* Removed unnecessary conditions and quotation marks.

* Added TODO about deciding what to do in debug mode.

* Added TODO about checking which currencies are allowed.

* Added information on parameters read from the first bid only.

* Fixed missing indexing operator when checking if mainSize is 0x0.

* Implemented tests for buildRequests.

* Implemented tests for interpretResponse.

* Implemented tests for onBidWon.

* Implemented tests for onTimeout.

* Added some missing semicolons.

* Removed TODO about allowed currencies.

* Removed setting test in debug mode and related TODOs.

* Removed optional pos parameter.

* Removed optional bidfloor parameter and use floorPrice instead of floorprice.

* Added support for multiple deal ID parameters.

* Fixed formatting.

* Added more explanation about the value of position param.

* Moved pos property into Kobler-specific banner extension.

* Simplifications based on PR comments.

* Use getRefererInfo to get page URL for timeout notifications.

* Removed TODO about auction type.

* Added information on how to generate a sample bid.

* Removed reading currency from currency.adServerCurrency.
  • Loading branch information
acsbendi authored Apr 21, 2021
1 parent eff2ae6 commit dc6b450
Show file tree
Hide file tree
Showing 3 changed files with 992 additions and 0 deletions.
231 changes: 231 additions & 0 deletions modules/koblerBidAdapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
import * as utils from '../src/utils.js';
import {config} from '../src/config.js';
import {registerBidder} from '../src/adapters/bidderFactory.js';
import {BANNER} from '../src/mediaTypes.js';
import {getRefererInfo} from '../src/refererDetection.js';

const BIDDER_CODE = 'kobler';
const BIDDER_ENDPOINT = 'https://bid.essrtb.com/bid/prebid_rtb_call';
const TIMEOUT_NOTIFICATION_ENDPOINT = 'https://bid.essrtb.com/notify/prebid_timeout';
const SUPPORTED_CURRENCY = 'USD';
const DEFAULT_TIMEOUT = 1000;
const TIME_TO_LIVE_IN_SECONDS = 10 * 60;

export const isBidRequestValid = function (bid) {
return !!(bid && bid.bidId && bid.params && bid.params.placementId);
};

export const buildRequests = function (validBidRequests, bidderRequest) {
return {
method: 'POST',
url: BIDDER_ENDPOINT,
data: buildOpenRtbBidRequestPayload(validBidRequests, bidderRequest),
options: {
contentType: 'application/json'
}
};
};

export const interpretResponse = function (serverResponse) {
const res = serverResponse.body;
const bids = []
if (res) {
res.seatbid.forEach(sb => {
sb.bid.forEach(b => {
bids.push({
requestId: b.impid,
cpm: b.price,
currency: res.cur,
width: b.w,
height: b.h,
creativeId: b.crid,
dealId: b.dealid,
netRevenue: true,
ttl: TIME_TO_LIVE_IN_SECONDS,
ad: b.adm,
nurl: b.nurl,
meta: {
advertiserDomains: b.adomain
}
})
})
});
}
return bids;
};

export const onBidWon = function (bid) {
const cpm = bid.cpm || 0;
const adServerPrice = utils.deepAccess(bid, 'adserverTargeting.hb_pb', 0);
if (utils.isStr(bid.nurl) && bid.nurl !== '') {
const winNotificationUrl = utils.replaceAuctionPrice(bid.nurl, cpm)
.replace(/\${AD_SERVER_PRICE}/g, adServerPrice);
utils.triggerPixel(winNotificationUrl);
}
};

export const onTimeout = function (timeoutDataArray) {
if (utils.isArray(timeoutDataArray)) {
const refererInfo = getRefererInfo();
const pageUrl = (refererInfo && refererInfo.referer)
? refererInfo.referer
: window.location.href;
timeoutDataArray.forEach(timeoutData => {
const query = utils.parseQueryStringParameters({
ad_unit_code: timeoutData.adUnitCode,
auction_id: timeoutData.auctionId,
bid_id: timeoutData.bidId,
timeout: timeoutData.timeout,
placement_id: utils.deepAccess(timeoutData, 'params.0.placementId'),
page_url: pageUrl,
});
const timeoutNotificationUrl = `${TIMEOUT_NOTIFICATION_ENDPOINT}?${query}`;
utils.triggerPixel(timeoutNotificationUrl);
});
}
};

function buildOpenRtbBidRequestPayload(validBidRequests, bidderRequest) {
const imps = validBidRequests.map(buildOpenRtbImpObject);
const timeout = bidderRequest.timeout || config.getConfig('bidderTimeout') || DEFAULT_TIMEOUT;
const pageUrl = (bidderRequest.refererInfo && bidderRequest.refererInfo.referer)
? bidderRequest.refererInfo.referer
: window.location.href;

const request = {
id: bidderRequest.auctionId,
at: 1,
tmax: timeout,
cur: [SUPPORTED_CURRENCY],
imp: imps,
device: {
devicetype: getDevice(),
geo: getGeo(validBidRequests[0])
},
site: {
page: pageUrl,
},
test: getTest(validBidRequests[0])
};

return JSON.stringify(request);
}

function buildOpenRtbImpObject(validBidRequest) {
const sizes = getSizes(validBidRequest);
const mainSize = sizes[0];
const floorInfo = getFloorInfo(validBidRequest, mainSize);

return {
id: validBidRequest.bidId,
banner: {
format: buildFormatArray(sizes),
w: mainSize[0],
h: mainSize[1],
ext: {
kobler: {
pos: getPosition(validBidRequest)
}
}
},
tagid: validBidRequest.params.placementId,
bidfloor: floorInfo.floor,
bidfloorcur: floorInfo.currency,
pmp: buildPmpObject(validBidRequest)
};
}

function getDevice() {
const ws = utils.getWindowSelf();
const ua = ws.navigator.userAgent;

if (/(tablet|ipad|playbook|silk|android 3.0|xoom|sch-i800|kindle)|(android(?!.*mobi))/i
.test(ua.toLowerCase())) {
return 5; // tablet
}
if (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series([46])0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i
.test(ua.toLowerCase())) {
return 4; // phone
}
return 2; // personal computers
}

function getGeo(validBidRequest) {
if (validBidRequest.params.zip) {
return {
zip: validBidRequest.params.zip
};
}
return {};
}

function getTest(validBidRequest) {
return validBidRequest.params.test ? 1 : 0;
}

function getSizes(validBidRequest) {
const sizes = utils.deepAccess(validBidRequest, 'mediaTypes.banner.sizes', validBidRequest.sizes);
if (utils.isArray(sizes) && sizes.length > 0) {
return sizes;
}

return [[0, 0]];
}

function buildFormatArray(sizes) {
return sizes.map(size => {
return {
w: size[0],
h: size[1]
};
});
}

function getPosition(validBidRequest) {
return parseInt(validBidRequest.params.position) || 0;
}

function getFloorInfo(validBidRequest, mainSize) {
if (typeof validBidRequest.getFloor === 'function') {
const sizeParam = mainSize[0] === 0 && mainSize[1] === 0 ? '*' : mainSize;
return validBidRequest.getFloor({
currency: SUPPORTED_CURRENCY,
mediaType: BANNER,
size: sizeParam
});
} else {
return {
currency: SUPPORTED_CURRENCY,
floor: getFloorPrice(validBidRequest)
};
}
}

function getFloorPrice(validBidRequest) {
return parseFloat(validBidRequest.params.floorPrice) || 0.0;
}

function buildPmpObject(validBidRequest) {
if (validBidRequest.params.dealIds) {
return {
deals: validBidRequest.params.dealIds.map(dealId => {
return {
id: dealId
};
})
};
}
return {};
}

export const spec = {
code: BIDDER_CODE,
supportedMediaTypes: [BANNER],
isBidRequestValid,
buildRequests,
interpretResponse,
onBidWon,
onTimeout
};

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

**Module Name**: Kobler Bidder Adapter
**Module Type**: Bidder Adapter
**Maintainer**: bidding-support@kobler.no

# Description

Connects to Kobler's demand sources.

This adapter currently only supports Banner Ads.

# Parameters

| Parameter (in `params`) | Scope | Type | Description | Example |
| --- | --- | --- | --- | --- |
| placementId | Required | String | The identifier of the placement, it has to be issued by Kobler. | `'xjer0ch8'` |
| zip | Optional | String | Zip code of the user or the medium. When multiple ad units are submitted together, it is enough to set this parameter on the first one. | `'102 22'` |
| test | Optional | Boolean | Whether the request is for testing only. When multiple ad units are submitted together, it is enough to set this parameter on the first one. Defaults to false. | `true` |
| floorPrice | Optional | Float | Floor price in CPM and USD. Can be used as an alternative to the [Floors module](https://docs.prebid.org/dev-docs/modules/floors.html), which is also supported by this adapter. Defaults to 0. | `5.0` |
| position | Optional | Integer | The position of the ad unit. Can be used to differentiate between ad units if the same placement ID is used across multiple ad units. The first ad unit should have a `position` of 0, the second one should have a `position` of 1 and so on. Defaults to 0. | `1` |
| dealIds | Optional | Array of Strings | Array of deal IDs. | `['abc328745', 'mxw243253']` |

# Test Parameters
```javascript
const adUnits = [{
code: 'div-gpt-ad-1460505748561-1',
mediaTypes: {
banner: {
sizes: [[320, 250], [300, 250]],
}
},
bids: [{
bidder: 'kobler',
params: {
placementId: 'k5H7et3R0'
}
}]
}];
```

In order to see a sample bid from Kobler (without a proper setup), you have to also do the following:
- Change the [`refererInfo` function](https://github.com/prebid/Prebid.js/blob/master/src/refererDetection.js) to return `'https://www.tv2.no/a/11734615'` as a [`referer`](https://github.com/prebid/Prebid.js/blob/caead3ccccc448e4cd09d074fd9f8833f56fe9b3/src/refererDetection.js#L169). This is necessary because Kobler only bids on recognized articles.
- Change the adapter's [`BIDDER_ENDPOINT`](https://github.com/prebid/Prebid.js/blob/master/modules/koblerBidAdapter.js#L8) to `'https://bid-service.dev.essrtb.com/bid/prebid_rtb_call'`. This endpoint belongs to the development server that is set up to always return a bid for the correct `placementId` and page URL combination.

# Test Optional Parameters
```javascript
const adUnits = [{
code: 'div-gpt-ad-1460505748561-1',
mediaTypes: {
banner: {
sizes: [[320, 250], [300, 250]],
}
},
bids: [{
bidder: 'kobler',
params: {
placementId: 'k5H7et3R0',
zip: '102 22',
test: true,
floorPrice: 5.0,
position: 1,
dealIds: ['abc328745', 'mxw243253']
}
}]
}];
```
Loading

0 comments on commit dc6b450

Please sign in to comment.