Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Kobler Bid Adapter: add new bid adapter #6479

Merged
merged 31 commits into from
Apr 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
bb33686
Implemented Kobler bidder adapter.
acsbendi Mar 11, 2021
87c0594
Added missing '' to dealId example in parameters table.
acsbendi Mar 11, 2021
c8b1461
Added information on supporting the Floors module.
acsbendi Mar 12, 2021
ec1a8e2
Implemented tests for isBidRequestValid.
acsbendi Mar 12, 2021
05bb12a
Implemented tests for inherited functions.
acsbendi Mar 12, 2021
4cdbad9
Removed unnecessary conditions and quotation marks.
acsbendi Mar 12, 2021
5918e2f
Added TODO about deciding what to do in debug mode.
acsbendi Mar 12, 2021
1a28e89
Added TODO about checking which currencies are allowed.
acsbendi Mar 12, 2021
c97e3ca
Added information on parameters read from the first bid only.
acsbendi Mar 12, 2021
4be7e1d
Fixed missing indexing operator when checking if mainSize is 0x0.
acsbendi Mar 12, 2021
da4d98b
Implemented tests for buildRequests.
acsbendi Mar 12, 2021
a3efb92
Implemented tests for interpretResponse.
acsbendi Mar 15, 2021
f359949
Implemented tests for onBidWon.
acsbendi Mar 15, 2021
0e55bcf
Implemented tests for onTimeout.
acsbendi Mar 15, 2021
2651d2d
Added some missing semicolons.
acsbendi Mar 15, 2021
cd7e4b0
Removed TODO about allowed currencies.
acsbendi Mar 15, 2021
00d29fb
Removed setting test in debug mode and related TODOs.
acsbendi Mar 17, 2021
e42c8d6
Removed optional pos parameter.
acsbendi Mar 18, 2021
39e62bf
Removed optional bidfloor parameter and use floorPrice instead of flo…
acsbendi Mar 18, 2021
ffbcbec
Added support for multiple deal ID parameters.
acsbendi Mar 23, 2021
9e8d90d
Merge pull request #2 from Essens/kobler-adapter
acsbendi Mar 24, 2021
6f5a1c1
Fixed formatting.
acsbendi Mar 26, 2021
450abe4
Merge remote-tracking branch 'upstream/master'
acsbendi Mar 26, 2021
5a0485b
Added more explanation about the value of position param.
acsbendi Apr 6, 2021
27c9ad7
Moved pos property into Kobler-specific banner extension.
acsbendi Apr 7, 2021
e37de50
Simplifications based on PR comments.
acsbendi Apr 8, 2021
cd3fca8
Use getRefererInfo to get page URL for timeout notifications.
acsbendi Apr 13, 2021
8e75720
Merge remote-tracking branch 'upstream/master'
acsbendi Apr 13, 2021
0ae9e5f
Removed TODO about auction type.
acsbendi Apr 13, 2021
866d57c
Added information on how to generate a sample bid.
acsbendi Apr 15, 2021
8e6a8a7
Removed reading currency from currency.adServerCurrency.
acsbendi Apr 20, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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';
msm0504 marked this conversation as resolved.
Show resolved Hide resolved
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