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

AOL Adapter - ONE Mobile endpoint implemented. #1115

Merged
merged 3 commits into from
May 2, 2017
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
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
225 changes: 193 additions & 32 deletions src/adapters/aol.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,130 @@ const utils = require('../utils.js');
const ajax = require('../ajax.js').ajax;
const bidfactory = require('../bidfactory.js');
const bidmanager = require('../bidmanager.js');
const constants = require('../constants.json');

const AolAdapter = function AolAdapter() {

let showCpmAdjustmentWarning = true;
const pubapiTemplate = template`${'protocol'}://${'host'}/pubapi/3.0/${'network'}/${'placement'}/${'pageid'}/${'sizeid'}/ADTECH;v=2;cmd=bid;cors=yes;alias=${'alias'}${'bidfloor'};misc=${'misc'}`;
const nexageBaseApiTemplate = template`${'protocol'}://${'host'}/bidRequest?`;
const nexageGetApiTemplate = template`dcn=${'dcn'}&pos=${'pos'}&cmd=bid${'ext'}`;
const BIDDER_CODE = 'aol';
const SERVER_MAP = {
const MP_SERVER_MAP = {
us: 'adserver-us.adtech.advertising.com',
eu: 'adserver-eu.adtech.advertising.com',
as: 'adserver-as.adtech.advertising.com'
};
const NEXAGE_SERVER = 'hb.nexage.com';
const SYNC_TYPES = {
iframe: 'IFRAME',
img: 'IMG'
};

let domReady = (() => {
let readyEventFired = false;
return fn => {
let idempotentFn = () => {
if (readyEventFired) {
return;
}
readyEventFired = true;
return fn();
};
let doScrollCheck = () => {
if (readyEventFired) {
return;
}
try {
document.documentElement.doScroll('left');
} catch (e) {
setTimeout(doScrollCheck, 1);
return;
}
return idempotentFn();
};
if (document.readyState === "complete") {
return idempotentFn();
}
if (document.addEventListener) {
document.addEventListener("DOMContentLoaded", idempotentFn, false);
window.addEventListener("load", idempotentFn, false);
} else if (document.attachEvent) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prebid.js only supports IE9+, so you should be fine with using addEventListener only.

document.attachEvent("onreadystatechange", idempotentFn);
window.attachEvent("onload", idempotentFn);
let topLevel = false;
try {
topLevel = window.frameElement === null;
} catch (e) {
}
if (document.documentElement.doScroll && topLevel) {
return doScrollCheck();
}
}
};
})();

function dropSyncCookies(pixels) {
let pixelElements = parsePixelItems(pixels);
renderPixelElements(pixelElements);
}

function parsePixelItems(pixels) {
let itemsRegExp = /(img|iframe)[\s\S]*?src\s*=\s*("|')(.*?)\2/gi;
let tagNameRegExp = /\w*(?=\s)/;
let srcRegExp = /src=("|')(.*?)\1/;
let pixelsItems = [];

if (pixels) {
let matchedItems = pixels.match(itemsRegExp);
if (matchedItems) {
matchedItems.forEach(item => {
let tagNameMatches = item.match(tagNameRegExp);
let sourcesPathMatches = item.match(srcRegExp);
if (tagNameMatches && sourcesPathMatches) {
pixelsItems.push({
tagName: tagNameMatches[0].toUpperCase(),
src: sourcesPathMatches[2]
});
}
});
}
}

return pixelsItems;
}

function renderPixelElements(pixelsElements) {
pixelsElements.forEach((element) => {
switch (element.tagName) {
case SYNC_TYPES.img:
return renderPixelImage(element);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

@vzhukovsky vzhukovsky Apr 28, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi. Thank you for the note.
If we reuse this method we will have to insert elements in DOM directly.
We would like to avoid it so we prefer to use own implementation.

case SYNC_TYPES.iframe:
return renderPixelIframe(element);
}
});
}

function renderPixelImage(pixelsItem) {
let image = new Image();
image.src = pixelsItem.src;
}

function renderPixelIframe(pixelsItem) {
let iframe = document.createElement('iframe');
iframe.width = 1;
iframe.height = 1;
iframe.style.display = 'none';
iframe.src = pixelsItem.src;
if (document.readyState === 'interactive' ||
document.readyState === 'complete') {
document.body.appendChild(iframe);
} else {
domReady(() => {
document.body.appendChild(iframe);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you're checking the readyState above, I think removing domReady and just using DOMContentLoaded should be sufficient here.

});
}
}

function template(strings, ...keys) {
return function(...values) {
Expand All @@ -26,21 +139,21 @@ const AolAdapter = function AolAdapter() {
};
}

function _buildPubapiUrl(bid) {
function _buildMarketplaceUrl(bid) {
const params = bid.params;
const serverParam = params.server;
let regionParam = params.region || 'us';
let server;

if (!SERVER_MAP.hasOwnProperty(regionParam)) {
if (!MP_SERVER_MAP.hasOwnProperty(regionParam)) {
utils.logWarn(`Unknown region '${regionParam}' for AOL bidder.`);
regionParam = 'us'; // Default region.
}

if (serverParam) {
server = serverParam;
} else {
server = SERVER_MAP[regionParam];
server = MP_SERVER_MAP[regionParam];
}

// Set region param, used by AOL analytics.
Expand All @@ -60,6 +173,22 @@ const AolAdapter = function AolAdapter() {
});
}

function _buildNexageApiUrl(bid) {
let {dcn, pos} = bid.params;
let nexageApi = nexageBaseApiTemplate({
protocol: (document.location.protocol === 'https:') ? 'https' : 'http',
host: bid.params.host || NEXAGE_SERVER
});
if (dcn && pos) {
let ext = '';
utils._each(bid.params.ext, (value, key) => {
ext += `&${key}=${encodeURIComponent(value)}`;
});
nexageApi += nexageGetApiTemplate({dcn, pos, ext});
}
return nexageApi;
}

function _addErrorBidResponse(bid, response = {}) {
const bidResponse = bidfactory.createBid(2, bid);
bidResponse.bidderCode = BIDDER_CODE;
Expand Down Expand Up @@ -94,7 +223,11 @@ const AolAdapter = function AolAdapter() {

let ad = bidData.adm;
if (response.ext && response.ext.pixels) {
ad += response.ext.pixels;
if (bid.params.userSyncOn === constants.EVENTS.BID_RESPONSE) {
dropSyncCookies(response.ext.pixels);
} else {
ad += response.ext.pixels;
}
}

const bidResponse = bidfactory.createBid(1, bid);
Expand All @@ -113,40 +246,68 @@ const AolAdapter = function AolAdapter() {
bidmanager.addBidResponse(bid.placementCode, bidResponse);
}

function _isNexageRequestPost(bid) {
if (bid.params.id && bid.params.imp && bid.params.imp[0]) {
let imp = bid.params.imp[0];
return imp.id && imp.tagid &&
((imp.banner && imp.banner.w && imp.banner.h) ||
(imp.video && imp.video.mimes && imp.video.minduration && imp.video.maxduration));
}
}

function _callBids(params) {
utils._each(params.bids, bid => {
const pubapiUrl = _buildPubapiUrl(bid);

ajax(pubapiUrl, response => {
// needs to be here in case bidderSettings are defined after requestBids() is called
if (showCpmAdjustmentWarning &&
$$PREBID_GLOBAL$$.bidderSettings && $$PREBID_GLOBAL$$.bidderSettings.aol &&
typeof $$PREBID_GLOBAL$$.bidderSettings.aol.bidCpmAdjustment === 'function'
) {
utils.logWarn(
'bidCpmAdjustment is active for the AOL adapter. ' +
'As of Prebid 0.14, AOL can bid in net – please contact your accounts team to enable.'
);
let apiUrl;
let data = null;
let options = {
withCredentials: true
};
let isNexageRequestPost = _isNexageRequestPost(bid);
if (bid.params.placement && bid.params.network) {
apiUrl = _buildMarketplaceUrl(bid);
} else if(bid.params.dcn && bid.params.pos || isNexageRequestPost) {
apiUrl = _buildNexageApiUrl(bid);
if (isNexageRequestPost) {
data = bid.params;
options.customHeaders = {
'x-openrtb-version': '2.2'
};
options.method = 'POST';
options.contentType = 'application/json';
}
showCpmAdjustmentWarning = false; // warning is shown at most once
}
if (apiUrl) {
ajax(apiUrl, response => {
// Needs to be here in case bidderSettings are defined after requestBids() is called
if (showCpmAdjustmentWarning &&
$$PREBID_GLOBAL$$.bidderSettings && $$PREBID_GLOBAL$$.bidderSettings.aol &&
typeof $$PREBID_GLOBAL$$.bidderSettings.aol.bidCpmAdjustment === 'function'
) {
utils.logWarn(
'bidCpmAdjustment is active for the AOL adapter. ' +
'As of Prebid 0.14, AOL can bid in net – please contact your accounts team to enable.'
);
}
showCpmAdjustmentWarning = false; // warning is shown at most once

if (!response && response.length <= 0) {
utils.logError('Empty bid response', BIDDER_CODE, bid);
_addErrorBidResponse(bid, response);
return;
}
if (!response && response.length <= 0) {
utils.logError('Empty bid response', BIDDER_CODE, bid);
_addErrorBidResponse(bid, response);
return;
}

try {
response = JSON.parse(response);
} catch (e) {
utils.logError('Invalid JSON in bid response', BIDDER_CODE, bid);
_addErrorBidResponse(bid, response);
return;
}
try {
response = JSON.parse(response);
} catch (e) {
utils.logError('Invalid JSON in bid response', BIDDER_CODE, bid);
_addErrorBidResponse(bid, response);
return;
}

_addBidResponse(bid, response);
_addBidResponse(bid, response);

}, null, { withCredentials: true });
}, data, options);
}
});
}

Expand Down
3 changes: 3 additions & 0 deletions src/ajax.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ export function ajax(url, callback, data, options = {}) {
if (options.withCredentials) {
x.withCredentials = true;
}
utils._each(options.customHeaders, (value, header) => {
x.setRequestHeader(header, value);
});
if (options.preflight) {
x.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
}
Expand Down
Loading