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

33Across: add viewability #3084

Merged
merged 27 commits into from
Sep 19, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
60b6821
check gdpr in buildRequest
curlyblueeagle Aug 3, 2018
888a1bd
User sync based on whether gdpr applies or not
curlyblueeagle Aug 3, 2018
59f8304
check if consent data exists during user sync
curlyblueeagle Aug 3, 2018
1096846
split user sync into further branches: 1) when gdpr does not apply 2)…
curlyblueeagle Aug 7, 2018
6dfb54d
Merge pull request #8 from 33Across/XCH-801_gdpr_compliance
curlyblueeagle Aug 16, 2018
c089d82
Merge remote-tracking branch 'upstream/master'
Sep 12, 2018
fc9e8c6
contribute viewability to ttxRequest
Sep 7, 2018
5a95872
update tests
Sep 10, 2018
8aa0218
remove window mock from tests
Sep 10, 2018
7f75405
use local variables
Sep 10, 2018
6aedf17
introduce ServerRequestBuilder
Sep 10, 2018
9765718
add withOptions() method to ServerRequestBuilder
Sep 10, 2018
88a6527
add semicolons
Sep 10, 2018
093062d
Merge pull request #12 from 33Across/viewability
glebglushtsov Sep 12, 2018
ac0b586
sync up package-lock.json with upstream/master
Sep 12, 2018
a9abbfb
stub window.top in tests
Sep 13, 2018
f701fb9
introduce getTopWindowSize() for test purpose
Sep 13, 2018
059437b
reformat code
Sep 13, 2018
90e5bed
add withSite() method to TtxRequestBuilder
Sep 13, 2018
9660102
Merge pull request #14 from 33Across/viewability
glebglushtsov Sep 13, 2018
832e1d1
Merge remote-tracking branch 'upstream/master'
Sep 14, 2018
f26b47a
add isIframe() and _isViewabilityMeasurable()
Sep 14, 2018
8f34683
handle NON_MEASURABLE viewability in nested iframes
Sep 14, 2018
07f4933
consider page visibility, stub utils functions getWindowTop() and get…
Sep 14, 2018
525684b
Merge pull request #15 from 33Across/viewability
glebglushtsov Sep 14, 2018
8507272
contribute viewability as 0 for inactive tab
Sep 17, 2018
d502156
Merge pull request #16 from 33Across/viewability
glebglushtsov Sep 17, 2018
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
165 changes: 148 additions & 17 deletions modules/33acrossBidAdapter.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
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';

const adapterState = {};

const NON_MEASURABLE = 'nm';

// All this assumes that only one bid is ever returned by ttx
function _createBidResponse(response) {
return {
Expand All @@ -23,26 +27,45 @@ function _createBidResponse(response) {
}
}

function _isViewabilityMeasurable() {
return !_isIframe();
}

function _getViewability(element, topWin, { w, h } = {}) {
return utils.getWindowTop().document.visibilityState === 'visible'
? _getPercentInView(element, topWin, { w, h })
: 0;
}

// Infer the necessary data from valid bid for a minimal ttxRequest and create HTTP request
// NOTE: At this point, TTX only accepts request for a single impression
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 viewabilityAmount = _isViewabilityMeasurable()
? _getViewability(element, utils.getWindowTop(), minSize)
: NON_MEASURABLE;

const contributeViewability = ViewabilityContributor(viewabilityAmount);

/*
* 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,12 +77,12 @@ 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) {
Expand All @@ -81,7 +104,7 @@ function _createServerRequest(bidRequest, gdprConsent) {
return {
'method': 'POST',
'url': url,
'data': JSON.stringify(ttxRequest),
'data': JSON.stringify(contributeViewability(ttxRequest)),
'options': options
}
}
Expand All @@ -97,11 +120,118 @@ 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: isNaN(viewabilityAmount) ? viewabilityAmount : Math.round(viewabilityAmount) };

return req;
}

return contributeViewability;
}

function _isIframe() {
try {
return utils.getWindowSelf() !== utils.getWindowTop();
} catch (e) {
return true;
}
}

Expand All @@ -122,9 +252,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 @@ -153,14 +283,15 @@ function getUserSyncs(syncOptions, responses, gdprConsent) {
}
}

const spec = {
export const spec = {
NON_MEASURABLE,

code: BIDDER_CODE,

isBidRequestValid,
buildRequests,
interpretResponse,
getUserSyncs
}
getUserSyncs,
};

registerBidder(spec);

module.exports = spec;
Loading