Skip to content

Commit

Permalink
Iqm Bid Adapter: add new bid adapter (#7111)
Browse files Browse the repository at this point in the history
  • Loading branch information
Params10 committed Jul 13, 2021
1 parent d517ed8 commit 39dde9a
Show file tree
Hide file tree
Showing 3 changed files with 577 additions and 20 deletions.
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']
}
}
}]
};
```

0 comments on commit 39dde9a

Please sign in to comment.