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

Iqm Bid Adapter: add new bid adapter #7111

Merged
merged 4 commits into from
Jul 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
290 changes: 290 additions & 0 deletions modules/iqmBidAdapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,290 @@
import {registerBidder} from '../src/adapters/bidderFactory.js';
import {config} from '../src/config.js';
import * as utils from '../src/utils.js';
import {BANNER, VIDEO} from '../src/mediaTypes.js';
import {INSTREAM} from '../src/video.js';

const BIDDER_CODE = 'iqm';
const VERSION = 'v.1.0.0';
const VIDEO_ORTB_PARAMS = [
'mimes',
'minduration',
'maxduration',
'placement',
'protocols',
'startdelay'
];
var ENDPOINT_URL = 'https://pbd.bids.iqm.com';

export const spec = {
supportedMediaTypes: [BANNER, VIDEO],
code: BIDDER_CODE,
aliases: ['iqm'],

/**
* 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.
*/
isBidRequestValid: function (bid) {
const banner = utils.deepAccess(bid, 'mediaTypes.banner');
const videoMediaType = utils.deepAccess(bid, 'mediaTypes.video');
const context = utils.deepAccess(bid, 'mediaTypes.video.context');
if ((videoMediaType && context === INSTREAM)) {
const videoBidderParams = utils.deepAccess(bid, 'params.video', {});

if (!Array.isArray(videoMediaType.playerSize)) {
return false;
}

if (!videoMediaType.context) {
return false;
}

const videoParams = {
...videoMediaType,
...videoBidderParams
};

if (!Array.isArray(videoParams.mimes) || videoParams.mimes.length === 0) {
return false;
}

if (!Array.isArray(videoParams.protocols) || videoParams.protocols.length === 0) {
return false;
}

if (
typeof videoParams.placement !== 'undefined' &&
typeof videoParams.placement !== 'number'
) {
return false;
}
if (
videoMediaType.context === INSTREAM &&
typeof videoParams.startdelay !== 'undefined' &&
typeof videoParams.startdelay !== 'number'
) {
return false;
}

return !!(bid && bid.params && bid.params.publisherId && bid.params.placementId);
} else {
if (banner === 'undefined') {
return false;
}
return !!(bid && bid.params && bid.params.publisherId && bid.params.placementId);
}
},
/**
* Takes an array of valid bid requests, all of which are guaranteed to have passed the isBidRequestValid() test.
*It prepares a bid request with the required information for the DSP side and sends this request to alloted endpoint
* parameter{validBidRequests, bidderRequest} bidderRequest object is useful because it carries a couple of bid parameters that are global to all the bids.
*/
buildRequests: function (validBidRequests, bidderRequest) {
return validBidRequests.map(bid => {
var finalRequest = {};
let bidfloor = utils.getBidIdParameter('bidfloor', bid.params);

const imp = {
id: bid.bidId,
secure: 1,
bidfloor: bidfloor || 0,
displaymanager: 'Prebid.js',
displaymanagerver: VERSION,

}
if (utils.deepAccess(bid, 'mediaTypes.banner')) {
imp.banner = getSize(bid.sizes);
imp.mediatype = 'banner';
} else if (utils.deepAccess(bid, 'mediaTypes.video')) {
imp.video = _buildVideoORTB(bid);
imp.mediatype = 'video';
}
const site = getSite(bid);
let device = getDevice(bid.params);
finalRequest = {
sizes: bid.sizes,
id: bid.bidId,
publisherId: utils.getBidIdParameter('publisherId', bid.params),
placementId: utils.getBidIdParameter('placementId', bid.params),
device: device,
site: site,
imp: imp,
auctionId: bid.auctionId,
adUnitCode: bid.adUnitCode,
bidderRequestId: bid.bidderRequestId,
uuid: bid.bidId,
bidderRequest
}
const request = {
method: 'POST',
url: ENDPOINT_URL,
data: finalRequest,
options: {
withCredentials: false
},

}
return request;
});
},
/**
* Takes Response from server as input and request.
*It parses the response from server side and generates bidresponses for with required rendering paramteres
* parameter{serverResponse, bidRequest} serverReponse: Response from the server side with ad creative.
*/
interpretResponse: function (serverResponse, bidRequest) {
const bidResponses = [];
serverResponse = serverResponse.body;
if (serverResponse && utils.isArray(serverResponse.seatbid)) {
utils._each(serverResponse.seatbid, function (bidList) {
utils._each(bidList.bid, function (bid) {
const responseCPM = parseFloat(bid.price);
if (responseCPM > 0.0 && bid.impid) {
const bidResponse = {
requestId: bidRequest.data.id,
currency: serverResponse.cur || 'USD',
cpm: responseCPM,
netRevenue: true,
creativeId: bid.crid || '',
adUnitCode: bidRequest.data.adUnitCode,
auctionId: bidRequest.data.auctionId,
mediaType: bidRequest.data.imp.mediatype,

ttl: bid.ttl || config.getConfig('_bidderTimeout')
};

if (bidRequest.data.imp.mediatype === VIDEO) {
bidResponse.width = bid.w || bidRequest.data.imp.video.w;
bidResponse.height = bid.h || bidRequest.data.imp.video.h;
bidResponse.adResponse = {
content: bid.adm,
height: bidRequest.data.imp.video.h,
width: bidRequest.data.imp.video.w
};

if (bidRequest.data.imp.video.context === INSTREAM) {
bidResponse.vastUrl = bid.adm;
}
} else if (bidRequest.data.imp.mediatype === BANNER) {
bidResponse.ad = bid.adm;
bidResponse.width = bid.w || bidRequest.data.imp.banner.w;
bidResponse.height = bid.h || bidRequest.data.imp.banner.h;
}
bidResponses.push(bidResponse);
}
})
});
}
return bidResponses;
},

};

let getDevice = function (bidparams) {
const language = navigator.language ? 'language' : 'userLanguage';
return {
geo: bidparams.geo,
h: screen.height,
w: screen.width,
dnt: _getDNT() ? 1 : 0,
language: navigator[language].split('-')[0],
make: navigator.vendor ? navigator.vendor : '',
ua: navigator.userAgent,
devicetype: _isMobile() ? 1 : _isConnectedTV() ? 3 : 2
};
};

let _getDNT = function () {
return navigator.doNotTrack === '1' || window.doNotTrack === '1' || navigator.msDoNotTrack === '1' || navigator.doNotTrack === 'yes';
};

let getSize = function (sizes) {
let sizeMap;
if (sizes.length === 2 && typeof sizes[0] === 'number' && typeof sizes[1] === 'number') {
sizeMap = {w: sizes[0], h: sizes[1]};
} else {
sizeMap = {w: sizes[0][0], h: sizes[0][1]};
}
return sizeMap;
};

function _isMobile() {
return (/(ios|ipod|ipad|iphone|android)/i).test(global.navigator.userAgent);
}

function _isConnectedTV() {
return (/(smart[-]?tv|hbbtv|appletv|googletv|hdmi|netcast\.tv|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b)/i).test(global.navigator.userAgent);
}

function getSite(bidderRequest) {
let domain = '';
let page = '';
let referrer = '';
const Id = 1;

const {refererInfo} = bidderRequest;

if (canAccessTopWindow()) {
const wt = utils.getWindowTop();
domain = wt.location.hostname;
page = wt.location.href;
referrer = wt.document.referrer || '';
} else if (refererInfo.reachedTop) {
const url = utils.parseUrl(refererInfo.referer);
domain = url.hostname;
page = refererInfo.referer;
} else if (refererInfo.stack && refererInfo.stack.length && refererInfo.stack[0]) {
const url = utils.parseUrl(refererInfo.stack[0]);
domain = url.hostname;
}

return {
domain,
page,
Id,
referrer
};
};

function canAccessTopWindow() {
try {
if (utils.getWindowTop().location.href) {
return true;
}
} catch (error) {
return false;
}
}

function _buildVideoORTB(bidRequest) {
const videoAdUnit = utils.deepAccess(bidRequest, 'mediaTypes.video');
const videoBidderParams = utils.deepAccess(bidRequest, 'params.video', {});
const video = {}

const videoParams = {
...videoAdUnit,
...videoBidderParams // Bidder Specific overrides
};
video.context = 1;
const {w, h} = getSize(videoParams.playerSize[0]);
video.w = w;
video.h = h;

VIDEO_ORTB_PARAMS.forEach((param) => {
if (videoParams.hasOwnProperty(param)) {
video[param] = videoParams[param];
}
});

video.placement = video.placement || 2;

video.startdelay = video.startdelay || 0;
video.placement = 1;
video.context = INSTREAM;

return video;
}
registerBidder(spec);
80 changes: 60 additions & 20 deletions modules/iqmBidAdapter.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@ Maintainer: hbteam@iqm.com

| Name | Scope | Description | Example |
| :------------ | :------- | :------------------------ | :------------------- |
| `publisherId` | required | The Publisher ID from iQM | "df5fd732-c5f3-11e7" |
| `tagId` | required | The tag ID from iQM | "1c5c9ec2-c5f4-11e7" |
| `placementId` | required | The Placement ID from iQM | "50cc36fe-c5f4-11e7" |
| `publisherId` | required | The Publisher ID from iQM | "df5fd732-c5f3-11e7-abc4-cec278b6b50a" |
| `placementId` | required | The Placement ID from iQM | 23451 |
| `bidfloor` | optional | Bid Floor | 0.50 |

# Description
Expand All @@ -21,21 +20,62 @@ Module that connects to iQM demand sources

# Test Parameters
```
var adUnits = [
{
code: 'test-div1',
sizes: [[320, 50]], // display 320x50
bids: [
{
bidder: 'iqm',
params: {
publisherId: 'df5fd732-c5f3-11e7-abc4-cec278b6b50a',
tagId: '1c5c9ec2-c5f4-11e7-abc4-cec278b6b50a',
placementId: '50cc36fe-c5f4-11e7-abc4-cec278b6b50a',
bidfloor: 0.50,
}
}
]
}
];
var adUnits = [{
code: 'div-gpt-ad-1460505748561-0',
mediaTypes: {
banner: {
sizes: [[300,250]]
}
},

bids: [{
bidder: 'iqm',
params: {
geo:{
country:'USA'
},

publisherId: 'df5fd732-c5f3-11e7-abc4-cec278b6b50a',
placementId: 23451,
bidfloor: 0.50
}
}]

}]

```

# adUnit Video

```
var videoAdUnit = {
code: 'video1',
mediaTypes: {
video: {
playerSize: [640, 480],
context: 'instream'
}
},
bids: [{
bidder: 'iqm',
params: {
// placementId: iosDevice ? 13239390 : 13232361, // Add your own placement id here. Note, skippable video is not supported on iOS
publisherId: 'df5fd732-c5f3-11e7-abc4-cec278b6b50a',
placementId: 23451,
geo:{
country:'USA'
},

bidfloor: 0.05,
video: {
placement :2,
mimes: ['video/mp4'],
protocols: [2,5],
skipppable: true,
playback_method: ['auto_play_sound_off']
}
}
}]
};

```