modules/yieldoneBidAdapter.js
import {deepAccess, isEmpty, isStr, logWarn, parseSizesInput} from '../src/utils.js';
import {registerBidder} from '../src/adapters/bidderFactory.js';
import {Renderer} from '../src/Renderer.js';
import {BANNER, VIDEO} from '../src/mediaTypes.js';
/**
* @typedef {import('../src/adapters/bidderFactory').Bid} Bid
* @typedef {import('../src/adapters/bidderFactory').BidRequest} BidRequest
* @typedef {import('../src/adapters/bidderFactory').BidderSpec} BidderSpec
* @typedef {import('../src/adapters/bidderFactory').ServerRequest} ServerRequest
* @typedef {import('../src/adapters/bidderFactory').ServerResponse} ServerResponse
* @typedef {import('../src/adapters/bidderFactory').SyncOptions} SyncOptions
* @typedef {import('../src/adapters/bidderFactory').UserSync} UserSync
* @typedef {import('../src/auction').BidderRequest} BidderRequest
*/
const BIDDER_CODE = 'yieldone';
const ENDPOINT_URL = 'https://y.one.impact-ad.jp/h_bid';
const USER_SYNC_URL = 'https://y.one.impact-ad.jp/push_sync';
const VIDEO_PLAYER_URL = 'https://img.ak.impact-ad.jp/ic/pone/ivt/firstview/js/dac-video-prebid.min.js';
const CMER_PLAYER_URL = 'https://an.cmertv.com/hb/renderer/cmertv-video-yone-prebid.min.js';
const VIEWABLE_PERCENTAGE_URL = 'https://img.ak.impact-ad.jp/ic/pone/ivt/firstview/js/prebid-adformat-config.js';
const DEFAULT_VIDEO_SIZE = {w: 640, h: 360};
/** @type BidderSpec */
export const spec = {
code: BIDDER_CODE,
aliases: ['y1'],
supportedMediaTypes: [BANNER, VIDEO],
/**
* Determines whether or not the given bid request is valid.
* @param {BidRequest} bid The bid params to validate.
* @returns {boolean} True if this is a valid bid, and false otherwise.
*/
isBidRequestValid: function(bid) {
return !!(bid.params.placementId);
},
/**
* Make a server request from the list of BidRequests.
* @param {Bid[]} validBidRequests - An array of bids.
* @param {BidderRequest} bidderRequest - bidder request object.
* @returns {ServerRequest|ServerRequest[]} ServerRequest Info describing the request to the server.
*/
buildRequests: function(validBidRequests, bidderRequest) {
return validBidRequests.map(bidRequest => {
const params = bidRequest.params;
const placementId = params.placementId;
const cb = Math.floor(Math.random() * 99999999999);
// TODO: is 'page' the right value here?
const referrer = bidderRequest.refererInfo.page;
const bidId = bidRequest.bidId;
const transactionId = bidRequest.ortb2Imp?.ext?.tid;
const unitCode = bidRequest.adUnitCode;
const timeout = bidderRequest.timeout;
const language = window.navigator.language;
const screenSize = window.screen.width + 'x' + window.screen.height;
const payload = {
v: 'hb1',
p: placementId,
cb: cb,
r: referrer,
uid: bidId,
tid: transactionId,
uc: unitCode,
tmax: timeout,
t: 'i',
language: language,
screen_size: screenSize
};
const mediaType = getMediaType(bidRequest);
switch (mediaType) {
case BANNER:
payload.sz = getBannerSizes(bidRequest);
break;
case VIDEO:
const videoSize = getVideoSize(bidRequest);
payload.w = videoSize.w;
payload.h = videoSize.h;
break;
default:
break;
}
// LiveRampID
const idlEnv = deepAccess(bidRequest, 'userId.idl_env');
if (isStr(idlEnv) && !isEmpty(idlEnv)) {
payload.lr_env = idlEnv;
}
// IMID
const imuid = deepAccess(bidRequest, 'userId.imuid');
if (isStr(imuid) && !isEmpty(imuid)) {
payload.imuid = imuid;
}
// DACID
const fuuid = deepAccess(bidRequest, 'userId.dacId.fuuid');
const dacid = deepAccess(bidRequest, 'userId.dacId.id');
if (isStr(fuuid) && !isEmpty(fuuid)) {
payload.fuuid = fuuid;
}
if (isStr(dacid) && !isEmpty(dacid)) {
payload.dac_id = dacid;
}
// ID5
const id5id = deepAccess(bidRequest, 'userId.id5id.uid');
if (isStr(id5id) && !isEmpty(id5id)) {
payload.id5Id = id5id;
}
return {
method: 'GET',
url: ENDPOINT_URL,
data: payload,
};
});
},
/**
* Unpack the response from the server into a list of bids.
* @param {ServerResponse} serverResponse - A successful response from the server.
* @param {BidRequest} bidRequests
* @returns {Bid[]} - An array of bids which were nested inside the server.
*/
interpretResponse: function(serverResponse, bidRequest) {
const bidResponses = [];
const response = serverResponse.body;
const crid = response.crid || 0;
const width = response.width || 0;
const height = response.height || 0;
const cpm = response.cpm * 1000 || 0;
if (width !== 0 && height !== 0 && cpm !== 0 && crid !== 0) {
const dealId = response.dealId || '';
const renderId = response.renderid || '';
const currency = response.currency || 'JPY';
const netRevenue = (response.netRevenue === undefined) ? true : response.netRevenue;
const referrer = bidRequest.data.r || '';
const bidResponse = {
requestId: response.uid,
cpm: cpm,
width: response.width,
height: response.height,
creativeId: crid,
dealId: dealId,
currency: currency,
netRevenue: netRevenue,
ttl: 60,
referrer: referrer,
meta: {
advertiserDomains: response.adomain ? response.adomain : []
},
};
if (response.adTag && renderId === 'ViewableRendering') {
bidResponse.mediaType = BANNER;
let viewableScript = `
<script src="${VIEWABLE_PERCENTAGE_URL}"></script>
<script>
let width =${bidResponse.width};
let height =${bidResponse.height};
let adTag = \`${response.adTag.replace(/\\/g, '\\\\').replace(/\//g, '\\/').replace(/'/g, "\\'").replace(/"/g, '\\"')}\`;
let targetId ="${bidRequest.data.uc}";
window.YONEPBViewable = {};
window.YONEPBViewable.executed = false;
const viewablePercentage = window.pb_conf.viewablePercentage;
const viewableRange = height * 0.01 * viewablePercentage;
const iframe = document.createElement('iframe');
iframe.setAttribute("style", "border: 0; margin: 0 auto; left: 0; top: 0; width:" + width + "px; height:" + height + "px;");
iframe.frameBorder = 0; iframe.scrolling = 'no';
const inDap = document.createElement('script');
inDap.innerHTML = "inDapIF = true;";
iframe.appendChild(inDap);
window.frameElement.parentElement.appendChild(iframe);
const doc = iframe.contentWindow ? iframe.contentWindow.document : iframe.contentDocument;
if(!window.parent.$sf){
let target = window.top.document.getElementById(targetId);
window.top.addEventListener('scroll', () => {
const targetRect = target.getBoundingClientRect();
if (!window.YONEPBViewable.executed && window.top.innerHeight - targetRect.top > viewableRange) {
window.YONEPBViewable.executed = true;
doc.open(); doc.write(adTag); doc.close();
window.frameElement.style.display = "none";
}
}, false);
}else{
let disp = function(){
if(!window.YONEPBViewable.executed && window.parent.$sf.ext.inViewPercentage() > viewablePercentage){
window.YONEPBViewable.executed = true;
doc.open(); doc.write(adTag); doc.close();
window.frameElement.style.display = "none";
}
let id = setTimeout(disp, 100);
if(window.YONEPBViewable.executed){clearTimeout(id);}
};
disp();
}
</script>
`;
bidResponse.ad = viewableScript;
} else if (response.adTag) {
bidResponse.mediaType = BANNER;
bidResponse.ad = response.adTag;
} else if (response.adm) {
bidResponse.mediaType = VIDEO;
bidResponse.vastXml = response.adm;
if (renderId === 'cmer') {
bidResponse.renderer = newCmerRenderer(response);
} else {
bidResponse.renderer = newRenderer(response);
}
}
bidResponses.push(bidResponse);
}
return bidResponses;
},
/**
* Register the user sync pixels which should be dropped after the auction.
* @param {SyncOptions} syncOptions Which user syncs are allowed?
* @returns {UserSync[]} The user syncs which should be dropped.
*/
getUserSyncs: function(syncOptions) {
if (syncOptions.iframeEnabled) {
return [{
type: 'iframe',
url: USER_SYNC_URL
}];
}
},
}
/**
* NOTE: server side does not yet support multiple formats.
* @param {Object} bidRequest -
* @param {boolean} [enabledOldFormat = true] - default: `true`.
* @returns {string|null} - `"banner"` or `"video"` or `null`.
*/
function getMediaType(bidRequest, enabledOldFormat = true) {
let hasBannerType = Boolean(deepAccess(bidRequest, 'mediaTypes.banner'));
let hasVideoType = Boolean(deepAccess(bidRequest, 'mediaTypes.video'));
if (enabledOldFormat) {
hasBannerType = hasBannerType || bidRequest.mediaType === BANNER ||
(isEmpty(bidRequest.mediaTypes) && isEmpty(bidRequest.mediaType));
hasVideoType = hasVideoType || bidRequest.mediaType === VIDEO;
}
if (hasBannerType && hasVideoType) {
const playerParams = deepAccess(bidRequest, 'params.playerParams');
if (playerParams) {
return VIDEO;
} else {
return BANNER;
}
} else if (hasBannerType) {
return BANNER;
} else if (hasVideoType) {
return VIDEO;
}
return null;
}
/**
* NOTE:
* If `mediaTypes.banner` exists, then `mediaTypes.banner.sizes` must also exist.
* The reason for this is that Prebid.js will perform the verification and
* if `mediaTypes.banner.sizes` is inappropriate, it will delete the entire `mediaTypes.banner`.
* @param {Object} bidRequest -
* @param {Object} bidRequest.banner -
* @param {Array<string>} bidRequest.banner.sizes -
* @param {boolean} [enabledOldFormat = true] - default: `true`.
* @returns {string} - strings like `"300x250"` or `"300x250,728x90"`.
*/
function getBannerSizes(bidRequest, enabledOldFormat = true) {
let sizes = deepAccess(bidRequest, 'mediaTypes.banner.sizes');
if (enabledOldFormat) {
sizes = sizes || bidRequest.sizes;
}
return parseSizesInput(sizes).join(',');
}
/**
* @param {Object} bidRequest -
* @param {boolean} [enabledOldFormat = true] - default: `true`.
* @param {boolean} [enabled1x1 = true] - default: `true`.
* @returns {{w: number, h: number}} -
*/
function getVideoSize(bidRequest, enabledOldFormat = true, enabled1x1 = true) {
/**
* @param {Array<number, number> | Array<Array<number, number>>} sizes -
* @return {{w: number, h: number} | null} -
*/
const _getPlayerSize = (sizes) => {
let result = null;
const size = parseSizesInput(sizes)[0];
if (isEmpty(size)) {
return result;
}
const splited = size.split('x');
const sizeObj = {w: parseInt(splited[0], 10), h: parseInt(splited[1], 10)};
const _isValidPlayerSize = !(isEmpty(sizeObj)) && (isFinite(sizeObj.w) && isFinite(sizeObj.h));
if (!_isValidPlayerSize) {
return result;
}
result = sizeObj;
return result;
}
let playerSize = _getPlayerSize(deepAccess(bidRequest, 'mediaTypes.video.playerSize'));
if (enabledOldFormat) {
playerSize = playerSize || _getPlayerSize(bidRequest.sizes);
}
if (enabled1x1) {
// NOTE: `video.playerSize` in 1x1 is always [1,1].
if (playerSize && (playerSize.w === 1 && playerSize.h === 1)) {
// NOTE: `params.playerSize` is a specific object to support `1x1`.
playerSize = _getPlayerSize(deepAccess(bidRequest, 'params.playerSize'));
}
}
return playerSize || DEFAULT_VIDEO_SIZE;
}
/**
* Create render for outstream video.
* @param {Object} serverResponse.body -
* @returns {Renderer} - Prebid Renderer object
*/
function newRenderer(response) {
const renderer = Renderer.install({
id: response.uid,
url: VIDEO_PLAYER_URL,
loaded: false,
});
try {
renderer.setRender(outstreamRender);
} catch (err) {
logWarn('Prebid Error calling setRender on newRenderer', err);
}
return renderer;
}
/**
* Handles an outstream response after the library is loaded
* @param {Object} bid
*/
function outstreamRender(bid) {
bid.renderer.push(() => {
window.DACIVTPREBID.renderPrebid(bid);
});
}
/**
* Create render for cmer outstream video.
* @param {Object} serverResponse.body -
* @returns {Renderer} - Prebid Renderer object
*/
function newCmerRenderer(response) {
const renderer = Renderer.install({
id: response.uid,
url: CMER_PLAYER_URL,
loaded: false,
});
try {
renderer.setRender(cmerRender);
} catch (err) {
logWarn('Prebid Error calling setRender on newRenderer', err);
}
return renderer;
}
/**
* Handles an outstream response after the library is loaded
* @param {Object} bid
*/
function cmerRender(bid) {
bid.renderer.push(() => {
window.CMERYONEPREBID.renderPrebid(bid);
});
}
registerBidder(spec);