-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
OMS Adapter: add new adapter (#10924)
* OMS Adapter: add new adapter * OMS Adapter: fix tests * OMS Adapter: required changes * OMS Adapter: change ttl * OMS Adapter: required changes
- Loading branch information
1 parent
ab7068e
commit d93032b
Showing
4 changed files
with
777 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
|
||
function getBoundingBox(element, {w, h} = {}) { | ||
let {width, height, left, top, right, bottom} = element.getBoundingClientRect(); | ||
|
||
if ((width === 0 || height === 0) && w && h) { | ||
width = w; | ||
height = h; | ||
right = left + w; | ||
bottom = top + h; | ||
} | ||
|
||
return {width, height, left, top, right, bottom}; | ||
} | ||
|
||
function getIntersectionOfRects(rects) { | ||
const bbox = { | ||
left: rects[0].left, right: rects[0].right, top: rects[0].top, bottom: rects[0].bottom | ||
}; | ||
|
||
for (let i = 1; i < rects.length; ++i) { | ||
bbox.left = Math.max(bbox.left, rects[i].left); | ||
bbox.right = Math.min(bbox.right, rects[i].right); | ||
|
||
if (bbox.left >= bbox.right) { | ||
return null; | ||
} | ||
|
||
bbox.top = Math.max(bbox.top, rects[i].top); | ||
bbox.bottom = Math.min(bbox.bottom, rects[i].bottom); | ||
|
||
if (bbox.top >= bbox.bottom) { | ||
return null; | ||
} | ||
} | ||
|
||
bbox.width = bbox.right - bbox.left; | ||
bbox.height = bbox.bottom - bbox.top; | ||
|
||
return bbox; | ||
} | ||
|
||
export const percentInView = (element, topWin, {w, h} = {}) => { | ||
const elementBoundingBox = getBoundingBox(element, {w, h}); | ||
|
||
// Obtain the intersection of the element and the viewport | ||
const elementInViewBoundingBox = getIntersectionOfRects([{ | ||
left: 0, top: 0, right: topWin.innerWidth, bottom: topWin.innerHeight | ||
}, elementBoundingBox]); | ||
|
||
let elementInViewArea, elementTotalArea; | ||
|
||
if (elementInViewBoundingBox !== null) { | ||
// Some or all of the element is in view | ||
elementInViewArea = elementInViewBoundingBox.width * elementInViewBoundingBox.height; | ||
elementTotalArea = elementBoundingBox.width * elementBoundingBox.height; | ||
|
||
return ((elementInViewArea / elementTotalArea) * 100); | ||
} | ||
|
||
// No overlap between element and the viewport; therefore, the element | ||
// lies completely out of view | ||
return 0; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,270 @@ | ||
import { | ||
isArray, | ||
getWindowTop, | ||
deepSetValue, | ||
logError, | ||
logWarn, | ||
createTrackPixelHtml, | ||
getWindowSelf, | ||
isFn, | ||
isPlainObject, | ||
getBidIdParameter, | ||
getUniqueIdentifierStr, | ||
} from '../src/utils.js'; | ||
import {registerBidder} from '../src/adapters/bidderFactory.js'; | ||
import {BANNER} from '../src/mediaTypes.js'; | ||
import {ajax} from '../src/ajax.js'; | ||
import {percentInView} from '../libraries/percentInView/percentInView.js'; | ||
|
||
const BIDDER_CODE = 'oms'; | ||
const URL = 'https://rt.marphezis.com/hb'; | ||
const TRACK_EVENT_URL = 'https://rt.marphezis.com/prebid' | ||
|
||
export const spec = { | ||
code: BIDDER_CODE, | ||
aliases: ['brightcom', 'bcmssp'], | ||
gvlid: 883, | ||
supportedMediaTypes: [BANNER], | ||
isBidRequestValid, | ||
buildRequests, | ||
interpretResponse, | ||
onBidderError, | ||
onBidWon, | ||
getUserSyncs, | ||
}; | ||
|
||
function buildRequests(bidReqs, bidderRequest) { | ||
try { | ||
const impressions = bidReqs.map(bid => { | ||
let bidSizes = bid?.mediaTypes?.banner?.sizes || bid.sizes; | ||
bidSizes = ((isArray(bidSizes) && isArray(bidSizes[0])) ? bidSizes : [bidSizes]); | ||
bidSizes = bidSizes.filter(size => isArray(size)); | ||
const processedSizes = bidSizes.map(size => ({w: parseInt(size[0], 10), h: parseInt(size[1], 10)})); | ||
|
||
const element = document.getElementById(bid.adUnitCode); | ||
const minSize = _getMinSize(processedSizes); | ||
const viewabilityAmount = _isViewabilityMeasurable(element) ? _getViewability(element, getWindowTop(), minSize) : 'na'; | ||
const viewabilityAmountRounded = isNaN(viewabilityAmount) ? viewabilityAmount : Math.round(viewabilityAmount); | ||
|
||
const imp = { | ||
id: bid.bidId, | ||
banner: { | ||
format: processedSizes, | ||
ext: { | ||
viewability: viewabilityAmountRounded | ||
} | ||
}, | ||
tagid: String(bid.adUnitCode) | ||
}; | ||
|
||
const bidFloor = _getBidFloor(bid); | ||
|
||
if (bidFloor) { | ||
imp.bidfloor = bidFloor; | ||
} | ||
|
||
return imp; | ||
}) | ||
|
||
const referrer = bidderRequest?.refererInfo?.page || ''; | ||
const publisherId = getBidIdParameter('publisherId', bidReqs[0].params); | ||
|
||
const payload = { | ||
id: getUniqueIdentifierStr(), | ||
imp: impressions, | ||
site: { | ||
domain: bidderRequest?.refererInfo?.domain || '', | ||
page: referrer, | ||
publisher: { | ||
id: publisherId | ||
} | ||
}, | ||
device: { | ||
devicetype: _getDeviceType(navigator.userAgent, bidderRequest?.ortb2?.device?.sua), | ||
w: screen.width, | ||
h: screen.height | ||
}, | ||
tmax: bidderRequest?.timeout | ||
}; | ||
|
||
if (bidderRequest?.gdprConsent) { | ||
deepSetValue(payload, 'regs.ext.gdpr', +bidderRequest.gdprConsent.gdprApplies); | ||
deepSetValue(payload, 'user.ext.consent', bidderRequest.gdprConsent.consentString); | ||
} | ||
|
||
const gpp = _getGpp(bidderRequest) | ||
if (gpp) { | ||
deepSetValue(payload, 'regs.ext.gpp', gpp); | ||
} | ||
|
||
if (bidderRequest?.ortb2?.regs?.coppa) { | ||
deepSetValue(payload, 'regs.coppa', 1); | ||
} | ||
|
||
if (bidReqs?.[0]?.schain) { | ||
deepSetValue(payload, 'source.ext.schain', bidReqs[0].schain) | ||
} | ||
|
||
if (bidderRequest?.ortb2?.user) { | ||
deepSetValue(payload, 'user', bidderRequest.ortb2.user) | ||
} | ||
|
||
if (bidReqs?.[0]?.userIdAsEids) { | ||
deepSetValue(payload, 'user.ext.eids', bidReqs[0].userIdAsEids || []) | ||
} | ||
|
||
if (bidReqs?.[0].userId) { | ||
deepSetValue(payload, 'user.ext.ids', bidReqs[0].userId || []) | ||
} | ||
|
||
if (bidderRequest?.ortb2?.site?.content) { | ||
deepSetValue(payload, 'site.content', bidderRequest.ortb2.site.content) | ||
} | ||
|
||
return { | ||
method: 'POST', | ||
url: URL, | ||
data: JSON.stringify(payload), | ||
}; | ||
} catch (e) { | ||
logError(e, {bidReqs, bidderRequest}); | ||
} | ||
} | ||
|
||
function isBidRequestValid(bid) { | ||
if (bid.bidder !== BIDDER_CODE || !bid.params || !bid.params.publisherId) { | ||
return false; | ||
} | ||
|
||
return true; | ||
} | ||
|
||
function interpretResponse(serverResponse) { | ||
let response = []; | ||
if (!serverResponse.body || typeof serverResponse.body != 'object') { | ||
logWarn('OMS server returned empty/non-json response: ' + JSON.stringify(serverResponse.body)); | ||
return response; | ||
} | ||
|
||
const {body: {id, seatbid}} = serverResponse; | ||
|
||
try { | ||
if (id && seatbid && seatbid.length > 0 && seatbid[0].bid && seatbid[0].bid.length > 0) { | ||
response = seatbid[0].bid.map(bid => { | ||
return { | ||
requestId: bid.impid, | ||
cpm: parseFloat(bid.price), | ||
width: parseInt(bid.w), | ||
height: parseInt(bid.h), | ||
creativeId: bid.crid || bid.id, | ||
currency: 'USD', | ||
netRevenue: true, | ||
mediaType: BANNER, | ||
ad: _getAdMarkup(bid), | ||
ttl: 300, | ||
meta: { | ||
advertiserDomains: bid?.adomain || [] | ||
} | ||
}; | ||
}); | ||
} | ||
} catch (e) { | ||
logError(e, {id, seatbid}); | ||
} | ||
|
||
return response; | ||
} | ||
|
||
// Don't do user sync for now | ||
function getUserSyncs(syncOptions, responses, gdprConsent) { | ||
return []; | ||
} | ||
|
||
function onBidderError(errorData) { | ||
if (errorData === null || !errorData.bidderRequest) { | ||
return; | ||
} | ||
|
||
_trackEvent('error', errorData.bidderRequest) | ||
} | ||
|
||
function onBidWon(bid) { | ||
if (bid === null) { | ||
return; | ||
} | ||
|
||
_trackEvent('bidwon', bid) | ||
} | ||
|
||
function _trackEvent(endpoint, data) { | ||
ajax(`${TRACK_EVENT_URL}/${endpoint}`, null, JSON.stringify(data), { | ||
method: 'POST', | ||
withCredentials: false | ||
}); | ||
} | ||
|
||
function _getDeviceType(ua, sua) { | ||
if (sua?.mobile || (/(ios|ipod|ipad|iphone|android)/i).test(ua)) { | ||
return 1 | ||
} | ||
|
||
if ((/(smart[-]?tv|hbbtv|appletv|googletv|hdmi|netcast\.tv|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b)/i).test(ua)) { | ||
return 3 | ||
} | ||
|
||
return 2 | ||
} | ||
|
||
function _getGpp(bidderRequest) { | ||
if (bidderRequest?.gppConsent != null) { | ||
return bidderRequest.gppConsent; | ||
} | ||
|
||
return ( | ||
bidderRequest?.ortb2?.regs?.gpp ?? { gppString: '', applicableSections: '' } | ||
); | ||
} | ||
|
||
function _getAdMarkup(bid) { | ||
let adm = bid.adm; | ||
if ('nurl' in bid) { | ||
adm += createTrackPixelHtml(bid.nurl); | ||
} | ||
return adm; | ||
} | ||
|
||
function _isViewabilityMeasurable(element) { | ||
return !_isIframe() && element !== null; | ||
} | ||
|
||
function _getViewability(element, topWin, {w, h} = {}) { | ||
return getWindowTop().document.visibilityState === 'visible' ? percentInView(element, topWin, {w, h}) : 0; | ||
} | ||
|
||
function _isIframe() { | ||
try { | ||
return getWindowSelf() !== getWindowTop(); | ||
} catch (e) { | ||
return true; | ||
} | ||
} | ||
|
||
function _getMinSize(sizes) { | ||
return sizes.reduce((min, size) => size.h * size.w < min.h * min.w ? size : min); | ||
} | ||
|
||
function _getBidFloor(bid) { | ||
if (!isFn(bid.getFloor)) { | ||
return bid.params.bidFloor ? bid.params.bidFloor : null; | ||
} | ||
|
||
let floor = bid.getFloor({ | ||
currency: 'USD', mediaType: '*', size: '*' | ||
}); | ||
if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'USD') { | ||
return floor.floor; | ||
} | ||
return null; | ||
} | ||
|
||
registerBidder(spec); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
# Overview | ||
|
||
``` | ||
Module Name: OMS Bid Adapter | ||
Module Type: Bidder Adapter | ||
Maintainer: alexandruc@onlinemediasolutions.com | ||
``` | ||
|
||
# Description | ||
|
||
Online media solutions adapter integration to the Prebid library. | ||
|
||
# Test Parameters | ||
|
||
``` | ||
var adUnits = [ | ||
{ | ||
code: 'test-leaderboard', | ||
mediaTypes: { | ||
banner: { | ||
sizes: [[728, 90]] | ||
} | ||
}, | ||
bids: [{ | ||
bidder: 'oms', | ||
params: { | ||
publisherId: 2141020, | ||
bidFloor: 0.01 | ||
} | ||
}] | ||
}, { | ||
code: 'test-banner', | ||
mediaTypes: { | ||
banner: { | ||
sizes: [[300, 250]] | ||
} | ||
}, | ||
bids: [{ | ||
bidder: 'oms', | ||
params: { | ||
publisherId: 2141020 | ||
} | ||
}] | ||
} | ||
] | ||
``` |
Oops, something went wrong.