mk9-prebid
Version:
Header Bidding Management Library
313 lines (260 loc) • 9.66 kB
JavaScript
import { registerBidder } from '../src/adapters/bidderFactory.js';
import * as utils from '../src/utils.js';
import { config } from '../src/config.js';
const VERSION = '3.4.1';
const BIDDER_CODE = 'sharethrough';
const STR_ENDPOINT = 'https://btlr.sharethrough.com/WYu2BXv1/v1';
const DEFAULT_SIZE = [1, 1];
// this allows stubbing of utility function that is used internally by the sharethrough adapter
export const sharethroughInternal = {
b64EncodeUnicode,
handleIframe,
isLockedInFrame,
getProtocol
};
export const sharethroughAdapterSpec = {
code: BIDDER_CODE,
isBidRequestValid: bid => !!bid.params.pkey && bid.bidder === BIDDER_CODE,
buildRequests: (bidRequests, bidderRequest) => {
return bidRequests.map(bidRequest => {
let query = {
placement_key: bidRequest.params.pkey,
bidId: bidRequest.bidId,
consent_required: false,
instant_play_capable: canAutoPlayHTML5Video(),
hbSource: 'prebid',
hbVersion: '$prebid.version$',
strVersion: VERSION,
};
const gpid = utils.deepAccess(bidRequest, 'ortb2Imp.ext.data.pbadslot');
if (gpid) {
query.gpid = gpid;
}
Object.assign(query, handleUniversalIds(bidRequest));
const nonHttp = sharethroughInternal.getProtocol().indexOf('http') < 0;
query.secure = nonHttp || (sharethroughInternal.getProtocol().indexOf('https') > -1);
if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.consentString) {
query.consent_string = bidderRequest.gdprConsent.consentString;
}
if (bidderRequest && bidderRequest.gdprConsent) {
query.consent_required = !!bidderRequest.gdprConsent.gdprApplies;
}
if (bidderRequest && bidderRequest.uspConsent) {
query.us_privacy = bidderRequest.uspConsent
}
if (config.getConfig('coppa') === true) {
query.coppa = true
}
if (bidRequest.schain) {
query.schain = JSON.stringify(bidRequest.schain);
}
const floor = getFloor(bidRequest);
if (floor) {
query.bidfloor = floor;
}
if (bidRequest.params.badv) {
query.badv = bidRequest.params.badv;
}
if (bidRequest.params.bcat) {
query.bcat = bidRequest.params.bcat;
}
// Data that does not need to go to the server,
// but we need as part of interpretResponse()
const strData = {
skipIframeBusting: bidRequest.params.iframe,
iframeSize: bidRequest.params.iframeSize,
sizes: bidRequest.sizes
};
return {
method: 'POST',
url: STR_ENDPOINT,
data: query,
strData: strData
};
})
},
interpretResponse: ({ body }, req) => {
if (!body || !body.creatives || !body.creatives.length) {
return [];
}
const creative = body.creatives[0];
let size = DEFAULT_SIZE;
if (req.strData.iframeSize || req.strData.sizes.length) {
size = req.strData.iframeSize
? req.strData.iframeSize
: getLargestSize(req.strData.sizes);
}
return [{
requestId: req.data.bidId,
width: size[0],
height: size[1],
cpm: creative.cpm,
creativeId: creative.creative.creative_key,
dealId: creative.creative.deal_id,
currency: 'USD',
netRevenue: true,
ttl: 360,
meta: { advertiserDomains: creative.creative && creative.creative.adomain ? creative.creative.adomain : [] },
ad: generateAd(body, req)
}];
},
getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => {
const syncParams = uspConsent ? `&us_privacy=${uspConsent}` : '';
const syncs = [];
const shouldCookieSync = syncOptions.pixelEnabled &&
serverResponses.length > 0 &&
serverResponses[0].body &&
serverResponses[0].body.cookieSyncUrls;
if (shouldCookieSync) {
serverResponses[0].body.cookieSyncUrls.forEach(url => {
syncs.push({ type: 'image', url: url + syncParams });
});
}
return syncs;
},
// Empty implementation for prebid core to be able to find it
onTimeout: (data) => {},
// Empty implementation for prebid core to be able to find it
onBidWon: (bid) => {},
// Empty implementation for prebid core to be able to find it
onSetTargeting: (bid) => {}
};
function handleUniversalIds(bidRequest) {
if (!bidRequest.userId) return {};
const universalIds = {};
const ttd = utils.deepAccess(bidRequest, 'userId.tdid');
if (ttd) universalIds.ttduid = ttd;
const pubc = utils.deepAccess(bidRequest, 'userId.pubcid') || utils.deepAccess(bidRequest, 'crumbs.pubcid');
if (pubc) universalIds.pubcid = pubc;
const idl = utils.deepAccess(bidRequest, 'userId.idl_env');
if (idl) universalIds.idluid = idl;
const id5 = utils.deepAccess(bidRequest, 'userId.id5id.uid');
if (id5) {
universalIds.id5uid = { id: id5 };
const id5link = utils.deepAccess(bidRequest, 'userId.id5id.ext.linkType');
if (id5link) universalIds.id5uid.linkType = id5link;
}
const lipb = utils.deepAccess(bidRequest, 'userId.lipb.lipbid');
if (lipb) universalIds.liuid = lipb;
return universalIds;
}
function getLargestSize(sizes) {
function area(size) {
return size[0] * size[1];
}
return sizes.reduce((prev, current) => {
if (area(current) > area(prev)) {
return current
} else {
return prev
}
});
}
function generateAd(body, req) {
const strRespId = `str_response_${req.data.bidId}`;
let adMarkup = `
<div data-str-native-key="${req.data.placement_key}" data-stx-response-name="${strRespId}">
</div>
<script>var ${strRespId} = "${b64EncodeUnicode(JSON.stringify(body))}"</script>
`;
if (req.strData.skipIframeBusting) {
// Don't break out of iframe
adMarkup = adMarkup + `<script src="https://native.sharethrough.com/assets/sfp.js"></script>`;
} else {
// Add logic to the markup that detects whether or not in top level document is accessible
// this logic will deploy sfp.js and/or iframe buster script(s) as appropriate
adMarkup = adMarkup + `
<script>
(${sharethroughInternal.isLockedInFrame.toString()})()
</script>
<script>
(${sharethroughInternal.handleIframe.toString()})()
</script>`;
}
return adMarkup;
}
function handleIframe () {
// only load iframe buster JS if we can access the top level document
// if we are 'locked in' to this frame then no point trying to bust out: we may as well render in the frame instead
var iframeBusterLoaded = false;
if (!window.lockedInFrame) {
var sfpIframeBusterJs = document.createElement('script');
sfpIframeBusterJs.src = 'https://native.sharethrough.com/assets/sfp-set-targeting.js';
sfpIframeBusterJs.type = 'text/javascript';
try {
window.document.getElementsByTagName('body')[0].appendChild(sfpIframeBusterJs);
iframeBusterLoaded = true;
} catch (e) {
utils.logError('Trouble writing frame buster script, error details:', e);
}
}
var clientJsLoaded = (!iframeBusterLoaded) ? !!(window.STR && window.STR.Tag) : !!(window.top.STR && window.top.STR.Tag);
if (!clientJsLoaded) {
var sfpJs = document.createElement('script');
sfpJs.src = 'https://native.sharethrough.com/assets/sfp.js';
sfpJs.type = 'text/javascript';
// only add sfp js to window.top if iframe busting successfully loaded; otherwise, add to iframe
try {
if (iframeBusterLoaded) {
window.top.document.getElementsByTagName('body')[0].appendChild(sfpJs);
} else {
window.document.getElementsByTagName('body')[0].appendChild(sfpJs);
}
} catch (e) {
utils.logError('Trouble writing sfp script, error details:', e);
}
}
}
// determines if we are capable of busting out of the iframe we are in
// if we catch a DOMException when trying to access top-level document, it means we're stuck in the frame we're in
function isLockedInFrame () {
window.lockedInFrame = false;
try {
window.lockedInFrame = !window.top.document;
} catch (e) {
window.lockedInFrame = (e instanceof DOMException);
}
}
// See https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding#The_Unicode_Problem
function b64EncodeUnicode(str) {
return btoa(
encodeURIComponent(str).replace(/%([0-9A-F]{2})/g,
function toSolidBytes(match, p1) {
return String.fromCharCode('0x' + p1);
}));
}
function canAutoPlayHTML5Video() {
const userAgent = navigator.userAgent;
if (!userAgent) return false;
const isAndroid = /Android/i.test(userAgent);
const isiOS = /iPhone|iPad|iPod/i.test(userAgent);
const chromeVersion = parseInt((/Chrome\/([0-9]+)/.exec(userAgent) || [0, 0])[1]);
const chromeiOSVersion = parseInt((/CriOS\/([0-9]+)/.exec(userAgent) || [0, 0])[1]);
const safariVersion = parseInt((/Version\/([0-9]+)/.exec(userAgent) || [0, 0])[1]);
if (
(isAndroid && chromeVersion >= 53) ||
(isiOS && (safariVersion >= 10 || chromeiOSVersion >= 53)) ||
!(isAndroid || isiOS)
) {
return true;
} else {
return false;
}
}
function getProtocol() {
return document.location.protocol;
}
function getFloor(bid) {
if (utils.isFn(bid.getFloor)) {
const floorInfo = bid.getFloor({
currency: 'USD',
mediaType: 'banner',
size: bid.sizes.map(size => ({ w: size[0], h: size[1] }))
});
if (utils.isPlainObject(floorInfo) && !isNaN(floorInfo.floor) && floorInfo.currency === 'USD') {
return parseFloat(floorInfo.floor);
}
}
return null;
}
registerBidder(sharethroughAdapterSpec);