Skip to content

Commit

Permalink
Merge pull request #12 from 33Across/viewability
Browse files Browse the repository at this point in the history
Viewability
  • Loading branch information
glebglushtsov authored Sep 12, 2018
2 parents c089d82 + 88a6527 commit 093062d
Show file tree
Hide file tree
Showing 2 changed files with 419 additions and 277 deletions.
135 changes: 121 additions & 14 deletions modules/33acrossBidAdapter.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { uniques } from 'src/utils';
import * as utils from 'src/utils';

const { registerBidder } = require('../src/adapters/bidderFactory');
const { config } = require('../src/config');

const BIDDER_CODE = '33across';
const END_POINT = 'https://ssc.33across.com/api/v1/hb';
const SYNC_ENDPOINT = 'https://de.tynt.com/deb/v2?m=xch&rt=html';
Expand Down Expand Up @@ -28,21 +30,28 @@ function _createBidResponse(response) {
function _createServerRequest(bidRequest, gdprConsent) {
const ttxRequest = {};
const params = bidRequest.params;
const element = document.getElementById(bidRequest.adUnitCode);
const sizes = _transformSizes(bidRequest.sizes);
const minSize = _getMinSize(sizes);

const contributeViewability = ViewabilityContributor(
_getPercentInView(element, window.top, minSize)
);

/*
* Infer data for the request payload
*/
ttxRequest.imp = [];
ttxRequest.imp[0] = {
banner: {
format: bidRequest.sizes.map(_getFormatSize)
format: sizes.map(size => Object.assign(size, {ext: {}}))
},
ext: {
ttx: {
prod: params.productId
}
}
}
};
ttxRequest.site = { id: params.siteId };

// Go ahead send the bidId in request to 33exchange so it's kept track of in the bid response and
Expand All @@ -54,19 +63,18 @@ function _createServerRequest(bidRequest, gdprConsent) {
ext: {
consent: gdprConsent.consentString
}
}
};
ttxRequest.regs = {
ext: {
gdpr: (gdprConsent.gdprApplies === true) ? 1 : 0
}
}
};

// Finally, set the openRTB 'test' param if this is to be a test bid
if (params.test === 1) {
ttxRequest.test = 1;
}


/*
* Now construct the full server request
*/
Expand All @@ -82,7 +90,7 @@ function _createServerRequest(bidRequest, gdprConsent) {
return {
'method': 'POST',
'url': url,
'data': JSON.stringify(ttxRequest),
'data': JSON.stringify(contributeViewability(ttxRequest)),
'options': options
}
}
Expand All @@ -98,12 +106,111 @@ function _createSync(siteId) {
}
}

function _getFormatSize(sizeArr) {
function _getSize(size) {
return {
w: sizeArr[0],
h: sizeArr[1],
ext: {}
w: parseInt(size[0], 10),
h: parseInt(size[1], 10)
}
}

function _getMinSize(sizes) {
return sizes.reduce((min, size) => size.h * size.w < min.h * min.w ? size : min);
}

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 _transformSizes(sizes) {
if (utils.isArray(sizes) && sizes.length === 2 && !utils.isArray(sizes[0])) {
return [_getSize(sizes)];
}

return sizes.map(_getSize);
}

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;
}

function _getPercentInView(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;
}

/**
* Viewability contribution to request..
*/
function ViewabilityContributor(viewabilityAmount) {
function contributeViewability(ttxRequest) {
const req = Object.assign({}, ttxRequest);
const imp = req.imp = req.imp.map(impItem => Object.assign({}, impItem));
const banner = imp[0].banner = Object.assign({}, imp[0].banner);
const ext = banner.ext = Object.assign({}, banner.ext);
const ttx = ext.ttx = Object.assign({}, ext.ttx);

ttx.viewability = { amount: Math.round(viewabilityAmount) };

return req;
}

return contributeViewability;
}

function isBidRequestValid(bid) {
Expand All @@ -123,9 +230,9 @@ function isBidRequestValid(bid) {
// - the server, at this point, also doesn't need the consent string to handle gdpr compliance. So passing
// value whether set or not, for the sake of future dev.
function buildRequests(bidRequests, bidderRequest) {
const gdprConsent = Object.assign({ consentString: undefined, gdprApplies: false }, bidderRequest && bidderRequest.gdprConsent)
const gdprConsent = Object.assign({ consentString: undefined, gdprApplies: false }, bidderRequest && bidderRequest.gdprConsent);

adapterState.uniqueSiteIds = bidRequests.map(req => req.params.siteId).filter(uniques);
adapterState.uniqueSiteIds = bidRequests.map(req => req.params.siteId).filter(utils.uniques);

return bidRequests.map((req) => {
return _createServerRequest(req, gdprConsent);
Expand Down Expand Up @@ -160,7 +267,7 @@ const spec = {
buildRequests,
interpretResponse,
getUserSyncs
}
};

registerBidder(spec);

Expand Down
Loading

0 comments on commit 093062d

Please sign in to comment.