modules/jixieBidAdapter.js
import {deepAccess, getDNT, isArray, logWarn, isFn, isPlainObject, logError, logInfo} from '../src/utils.js';
import {config} from '../src/config.js';
import {registerBidder} from '../src/adapters/bidderFactory.js';
import {getStorageManager} from '../src/storageManager.js';
import {BANNER, VIDEO} from '../src/mediaTypes.js';
import {ajax} from '../src/ajax.js';
import {getRefererInfo} from '../src/refererDetection.js';
import {Renderer} from '../src/Renderer.js';
const ADAPTER_VERSION = '2.1.0';
const PREBID_VERSION = '$prebid.version$';
const BIDDER_CODE = 'jixie';
export const storage = getStorageManager({bidderCode: BIDDER_CODE});
const JX_OUTSTREAM_RENDERER_URL = 'https://scripts.jixie.media/jxhbrenderer.1.1.min.js';
const REQUESTS_URL = 'https://hb.jixie.io/v2/hbpost';
const sidTTLMins_ = 30;
/**
* Get bid floor from Price Floors Module
*
* @param {Object} bid
* @returns {float||null}
*/
function getBidFloor(bid) {
if (!isFn(bid.getFloor)) {
return null;
}
let floor = bid.getFloor({
currency: 'USD',
mediaType: '*',
size: '*'
});
if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'USD') {
return floor.floor;
}
return null;
}
/**
* Own miscellaneous support functions:
*/
function setIds_(clientId, sessionId) {
let dd = null;
try {
dd = window.location.hostname.match(/[^.]*\.[^.]{2,3}(?:\.[^.]{2,3})?$/mg);
} catch (err1) {}
try {
let expC = (new Date(new Date().setFullYear(new Date().getFullYear() + 1))).toUTCString();
let expS = (new Date(new Date().setMinutes(new Date().getMinutes() + sidTTLMins_))).toUTCString();
storage.setCookie('_jxx', clientId, expC, 'None', null);
storage.setCookie('_jxx', clientId, expC, 'None', dd);
storage.setCookie('_jxxs', sessionId, expS, 'None', null);
storage.setCookie('_jxxs', sessionId, expS, 'None', dd);
storage.setDataInLocalStorage('_jxx', clientId);
storage.setDataInLocalStorage('_jxxs', sessionId);
} catch (error) {}
}
/**
* fetch some ids from cookie, LS.
* @returns
*/
const defaultGenIds_ = [
{ id: '_jxtoko' },
{ id: '_jxifo' },
{ id: '_jxtdid' },
{ id: '_jxcomp' }
];
function fetchIds_(cfg) {
let ret = {
client_id_c: '',
client_id_ls: '',
session_id_c: '',
session_id_ls: '',
jxeids: {}
};
try {
let tmp = storage.getCookie('_jxx');
if (tmp) ret.client_id_c = tmp;
tmp = storage.getCookie('_jxxs');
if (tmp) ret.session_id_c = tmp;
tmp = storage.getDataFromLocalStorage('_jxx');
if (tmp) ret.client_id_ls = tmp;
tmp = storage.getDataFromLocalStorage('_jxxs');
if (tmp) ret.session_id_ls = tmp;
let arr = cfg.genids ? cfg.genids : defaultGenIds_;
arr.forEach(function(o) {
tmp = storage.getCookie(o.ck ? o.ck : o.id);
if (tmp) ret.jxeids[o.id] = tmp;
});
} catch (error) {}
return ret;
}
// device in the payload had been a simple string ('desktop', 'mobile')
// Now changed to an object. yes the backend is able to handle it.
function getDevice_() {
const device = config.getConfig('device') || {};
device.w = device.w || window.innerWidth;
device.h = device.h || window.innerHeight;
device.ua = device.ua || navigator.userAgent;
device.dnt = getDNT() ? 1 : 0;
device.language = (navigator && navigator.language) ? navigator.language.split('-')[0] : '';
return device;
}
function jxOutstreamRender_(bidAd) {
bidAd.renderer.push(() => {
window.JixieOutstreamVideo.init({
sizes: [bidAd.width, bidAd.height],
width: bidAd.width,
height: bidAd.height,
targetId: bidAd.adUnitCode,
adResponse: bidAd.adResponse
});
});
}
function createRenderer_(bidAd, scriptUrl, createFcn) {
const renderer = Renderer.install({
id: bidAd.adUnitCode,
url: scriptUrl,
loaded: false,
config: {'player_height': bidAd.height, 'player_width': bidAd.width},
adUnitCode: bidAd.adUnitCode
});
try {
renderer.setRender(createFcn);
} catch (err) {
logWarn('Prebid Error calling setRender on renderer', err);
}
return renderer;
}
function getMiscDims_() {
let ret = {
pageurl: '',
domain: '',
device: 'unknown',
mkeywords: ''
}
try {
// TODO: this should pick refererInfo from bidderRequest
let refererInfo_ = getRefererInfo();
// TODO: does the fallback make sense here?
let url_ = refererInfo_?.page || window.location.href
ret.pageurl = url_;
ret.domain = refererInfo_?.domain || window.location.host
ret.device = getDevice_();
let keywords = document.getElementsByTagName('meta')['keywords'];
if (keywords && keywords.content) {
ret.mkeywords = keywords.content;
}
} catch (error) {}
return ret;
}
// easier for replacement in the unit test
export const internal = {
getDevice: getDevice_,
getRefererInfo: getRefererInfo,
ajax: ajax,
getMiscDims: getMiscDims_
};
export const spec = {
code: BIDDER_CODE,
supportedMediaTypes: [BANNER, VIDEO],
isBidRequestValid: function(bid) {
if (bid.bidder !== BIDDER_CODE || typeof bid.params === 'undefined') {
return false;
}
if (typeof bid.params.unit === 'undefined') {
return false;
}
return true;
},
buildRequests: function(validBidRequests, bidderRequest) {
const currencyObj = config.getConfig('currency');
const currency = (currencyObj && currencyObj.adServerCurrency) || 'USD';
let bids = [];
validBidRequests.forEach(function(one) {
let gpid = deepAccess(one, 'ortb2Imp.ext.gpid', deepAccess(one, 'ortb2Imp.ext.data.pbadslot', ''));
let tmp = {
bidId: one.bidId,
adUnitCode: one.adUnitCode,
mediaTypes: (one.mediaTypes === 'undefined' ? {} : one.mediaTypes),
sizes: (one.sizes === 'undefined' ? [] : one.sizes),
params: one.params,
gpid: gpid
};
let bidFloor = getBidFloor(one);
if (bidFloor) {
tmp.bidFloor = bidFloor;
}
bids.push(tmp);
});
let jxCfg = config.getConfig('jixie') || {};
let ids = fetchIds_(jxCfg);
let eids = [];
let miscDims = internal.getMiscDims();
let schain = deepAccess(validBidRequests[0], 'schain');
let eids1 = validBidRequests[0].userIdAsEids;
// all available user ids are sent to our backend in the standard array layout:
if (eids1 && eids1.length) {
eids = eids1;
}
// we want to send this blob of info to our backend:
let transformedParams = Object.assign({}, {
// TODO: fix auctionId leak: https://github.com/prebid/Prebid.js/issues/9781
auctionid: bidderRequest.auctionId || '',
aid: jxCfg.aid || '',
timeout: bidderRequest.timeout,
currency: currency,
timestamp: (new Date()).getTime(),
device: miscDims.device,
domain: miscDims.domain,
pageurl: miscDims.pageurl,
mkeywords: miscDims.mkeywords,
bids: bids,
eids: eids,
schain: schain,
pricegranularity: (config.getConfig('priceGranularity') || {}),
ver: ADAPTER_VERSION,
pbjsver: PREBID_VERSION,
cfg: jxCfg
}, ids);
return Object.assign({}, {
method: 'POST',
url: REQUESTS_URL,
data: JSON.stringify(transformedParams),
currency: currency
});
},
onTimeout: function(timeoutData) {
logError('jixie adapter timed out for the auction.', timeoutData);
},
onBidWon: function(bid) {
if (bid.trackingUrl) {
internal.ajax(bid.trackingUrl, null, {}, {
withCredentials: true,
method: 'GET',
crossOrigin: true
});
}
logInfo(
`jixie adapter won the auction. Bid id: ${bid.bidId}, Ad Unit Id: ${bid.adUnitId}`
);
},
interpretResponse: function(response, bidRequest) {
if (response && response.body && isArray(response.body.bids)) {
const bidResponses = [];
response.body.bids.forEach(function(oneBid) {
let bnd = {};
Object.assign(bnd, oneBid);
if (oneBid.osplayer) {
bnd.adResponse = {
content: oneBid.vastXml,
parameters: oneBid.osparams,
height: oneBid.height,
width: oneBid.width
};
let rendererScript = (oneBid.osparams.script ? oneBid.osparams.script : JX_OUTSTREAM_RENDERER_URL);
bnd.renderer = createRenderer_(oneBid, rendererScript, jxOutstreamRender_);
}
// a note on advertiserDomains: our adserver is not responding in
// openRTB-type json. so there is no need to copy from 'adomain' over
// to meta: advertiserDomains
// However, we will just make sure the property is there.
if (!bnd.meta) {
bnd.meta = {};
}
if (!bnd.meta.advertiserDomains) {
bnd.meta.advertiserDomains = [];
}
bidResponses.push(bnd);
});
if (response.body.setids) {
setIds_(response.body.setids.client_id,
response.body.setids.session_id);
}
return bidResponses;
} else { return []; }
},
getUserSyncs: function(syncOptions, serverResponses) {
if (!serverResponses.length || !serverResponses[0].body || !serverResponses[0].body.userSyncs) {
return false;
}
let syncs = [];
serverResponses[0].body.userSyncs.forEach(function(sync) {
if (syncOptions.iframeEnabled) {
syncs.push(sync.uf ? { url: sync.uf, type: 'iframe' } : { url: sync.up, type: 'image' });
} else if (syncOptions.pixelEnabled && sync.up) {
syncs.push({url: sync.up, type: 'image'})
}
})
return syncs;
}
}
registerBidder(spec);