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

IX Bid Adapter: capture errors in LS and send errors in request #7630

Merged
merged 2 commits into from
Nov 3, 2021
Merged
Show file tree
Hide file tree
Changes from all 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
162 changes: 144 additions & 18 deletions modules/ixBidAdapter.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { deepAccess, parseGPTSingleSizeArray, inIframe, deepClone, logError, logWarn, isFn, contains, isInteger, isArray, deepSetValue, parseQueryStringParameters, isEmpty, mergeDeep, convertTypes } from '../src/utils.js';
import { BANNER, VIDEO } from '../src/mediaTypes.js';
import { config } from '../src/config.js';
import { EVENTS } from '../src/constants.json';
import { getStorageManager } from '../src/storageManager.js';
import events from '../src/events.js';
import find from 'core-js-pure/features/array/find.js';
import { registerBidder } from '../src/adapters/bidderFactory.js';
import { INSTREAM, OUTSTREAM } from '../src/video.js';
Expand All @@ -20,23 +23,30 @@ const VIDEO_TIME_TO_LIVE = 3600; // 1hr
const NET_REVENUE = true;
const MAX_REQUEST_SIZE = 8000;
const MAX_REQUEST_LIMIT = 4;

const PRICE_TO_DOLLAR_FACTOR = {
JPY: 1
};
const USER_SYNC_URL = 'https://js-sec.indexww.com/um/ixmatch.html';
const RENDERER_URL = 'https://js-sec.indexww.com/htv/video-player.js';
const FLOOR_SOURCE = { PBJS: 'p', IX: 'x' };
// determines which eids we send and the rtiPartner field in ext

export const ERROR_CODES = {
BID_SIZE_INVALID_FORMAT: 1,
BID_SIZE_NOT_INCLUDED: 2,
PROPERTY_NOT_INCLUDED: 3,
SITE_ID_INVALID_VALUE: 4,
BID_FLOOR_INVALID_FORMAT: 5,
IX_FPD_EXCEEDS_MAX_SIZE: 6,
EXCEEDS_MAX_SIZE: 7,
PB_FPD_EXCEEDS_MAX_SIZE: 8,
VIDEO_DURATION_INVALID: 9
};
const FIRST_PARTY_DATA = {
SITE: [
'id', 'name', 'domain', 'cat', 'sectioncat', 'pagecat', 'page', 'ref', 'search', 'mobile',
'privacypolicy', 'publisher', 'content', 'keywords', 'ext'
],
USER: ['id', 'buyeruid', 'yob', 'gender', 'keywords', 'customdata', 'geo', 'data', 'ext']
};

const SOURCE_RTI_MAPPING = {
'liveramp.com': 'idl',
'netid.de': 'NETID',
Expand All @@ -45,7 +55,6 @@ const SOURCE_RTI_MAPPING = {
'uidapi.com': 'UID2',
'adserver.org': 'TDID'
};

const PROVIDERS = [
'britepoolid',
'id5id',
Expand All @@ -62,9 +71,7 @@ const PROVIDERS = [
'TDID',
'flocId'
];

const REQUIRED_VIDEO_PARAMS = ['mimes', 'minduration', 'maxduration']; // note: protocol/protocols is also reqd

const VIDEO_PARAMS_ALLOW_LIST = [
'mimes', 'minduration', 'maxduration', 'protocols', 'protocol',
'startdelay', 'placement', 'linearity', 'skip', 'skipmin',
Expand All @@ -73,6 +80,9 @@ const VIDEO_PARAMS_ALLOW_LIST = [
'delivery', 'pos', 'companionad', 'api', 'companiontype', 'ext',
'playerSize', 'w', 'h'
];
const LOCAL_STORAGE_KEY = 'ixdiag';
let hasRegisteredHandler = false;
export const storage = getStorageManager(GLOBAL_VENDOR_ID, BIDDER_CODE);

/**
* Transform valid bid request config object to banner impression object that will be sent to ad server.
Expand Down Expand Up @@ -125,7 +135,10 @@ function bidToVideoImp(bid) {
}

if (imp.video.minduration > imp.video.maxduration) {
logError(`IX Bid Adapter: video minduration [${imp.video.minduration}] cannot be greater than video maxduration [${imp.video.maxduration}]`);
logError(
`IX Bid Adapter: video minduration [${imp.video.minduration}] cannot be greater than video maxduration [${imp.video.maxduration}]`,
{ bidder: BIDDER_CODE, code: ERROR_CODES.VIDEO_DURATION_INVALID }
);
return {};
}

Expand Down Expand Up @@ -497,6 +510,13 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) {
r.ext.ixdiag[key] = ixdiag[key];
}

// Get cached errors stored in LocalStorage
const cachedErrors = getCachedErrors();

if (!isEmpty(cachedErrors)) {
r.ext.ixdiag.err = cachedErrors;
}

// if an schain is provided, send it along
if (validBidRequests[0].schain) {
r.source = {
Expand Down Expand Up @@ -575,7 +595,7 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) {
const baseRequestSize = `${baseUrl}${parseQueryStringParameters({ ...payload, r: JSON.stringify(r) })}`.length;

if (baseRequestSize > MAX_REQUEST_SIZE) {
logError('ix bidder: Base request size has exceeded maximum request size.');
logError('IX Bid Adapter: Base request size has exceeded maximum request size.', { bidder: BIDDER_CODE, code: ERROR_CODES.EXCEEDS_MAX_SIZE });
return requests;
}

Expand Down Expand Up @@ -605,7 +625,7 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) {
}
currentRequestSize += fpdRequestSize;
} else {
logError('ix bidder: IX config FPD request size has exceeded maximum request size.');
logError('IX Bid Adapter: IX config FPD request size has exceeded maximum request size.', { bidder: BIDDER_CODE, code: ERROR_CODES.IX_FPD_EXCEEDS_MAX_SIZE });
}
}

Expand Down Expand Up @@ -706,7 +726,7 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) {
const fpdRequestSize = encodeURIComponent(JSON.stringify({ ...site, ...user })).length;
currentRequestSize += fpdRequestSize;
} else {
logError('ix bidder: FPD request size has exceeded maximum request size.');
logError('IX Bid Adapter: FPD request size has exceeded maximum request size.', { bidder: BIDDER_CODE, code: ERROR_CODES.PB_FPD_EXCEEDS_MAX_SIZE });
}
}

Expand Down Expand Up @@ -939,10 +959,99 @@ function createMissingBannerImp(bid, imp, newSize) {
}

/**
* @typedef {Array[message: string, err: Object<bidder: string, code: number>]} ErrorData
* @property {string} message - The error message.
* @property {object} err - The error object.
* @property {string} err.bidder - The bidder of the error.
* @property {string} err.code - The error code.
*/

/**
* Error Event handler that receives type and arguments in a data object.
*
* @param {ErrorData} data
*/
function errorEventHandler(data) {
if (!storage.localStorageIsEnabled()) {
return;
}

let currentStorage;

try {
currentStorage = JSON.parse(storage.getDataFromLocalStorage(LOCAL_STORAGE_KEY) || '{}');
} catch (e) {
logWarn('ix can not read ixdiag from localStorage.');
}

const todayDate = new Date();

Object.keys(currentStorage).map((errorDate) => {
const date = new Date(errorDate);

if (date.setDate(date.getDate() + 7) - todayDate < 0) {
delete currentStorage[errorDate];
}
});

if (data.type === 'ERROR' && data.arguments && data.arguments[1] && data.arguments[1].bidder === BIDDER_CODE) {
const todayString = todayDate.toISOString().slice(0, 10);

const errorCode = data.arguments[1].code;

if (errorCode) {
currentStorage[todayString] = currentStorage[todayString] || {};

if (!Number(currentStorage[todayString][errorCode])) {
currentStorage[todayString][errorCode] = 0;
}

currentStorage[todayString][errorCode]++;
};
}

storage.setDataInLocalStorage(LOCAL_STORAGE_KEY, JSON.stringify(currentStorage));
}

/**
* Get ixdiag stored in LocalStorage and format to be added to request payload
*
* @returns {Object} Object with error codes and counts
*/
function getCachedErrors() {
if (!storage.localStorageIsEnabled()) {
return;
}

const errors = {};
let currentStorage;

try {
currentStorage = JSON.parse(storage.getDataFromLocalStorage(LOCAL_STORAGE_KEY) || '{}');
} catch (e) {
logError('ix can not read ixdiag from localStorage.');
return null;
}

Object.keys(currentStorage).forEach((date) => {
Object.keys(currentStorage[date]).forEach((code) => {
if (typeof currentStorage[date][code] === 'number') {
errors[code] = errors[code]
? errors[code] + currentStorage[date][code]
: currentStorage[date][code];
}
});
});

return errors;
}

/**
*
* Initialize Outstream Renderer
* @param {Object} bid
*/
function outstreamRenderer (bid) {
function outstreamRenderer(bid) {
bid.renderer.push(() => {
var config = {
width: bid.width,
Expand All @@ -959,7 +1068,7 @@ function outstreamRenderer (bid) {
* @param {string} id
* @returns {Renderer}
*/
function createRenderer (id) {
function createRenderer(id) {
const renderer = Renderer.install({
id: id,
url: RENDERER_URL,
Expand Down Expand Up @@ -993,6 +1102,12 @@ export const spec = {
* @return {boolean} True if this is a valid bid, and false otherwise.
*/
isBidRequestValid: function (bid) {
if (!hasRegisteredHandler) {
events.on(EVENTS.AUCTION_DEBUG, errorEventHandler);
events.on(EVENTS.AD_RENDER_FAILED, errorEventHandler);
hasRegisteredHandler = true;
}

const paramsVideoRef = deepAccess(bid, 'params.video');
const paramsSize = deepAccess(bid, 'params.size');
const mediaTypeBannerSizes = deepAccess(bid, 'mediaTypes.banner.sizes');
Expand All @@ -1002,6 +1117,7 @@ export const spec = {
const hasBidFloorCur = bid.params.hasOwnProperty('bidFloorCur');

if (bid.hasOwnProperty('mediaType') && !(contains(SUPPORTED_AD_TYPES, bid.mediaType))) {
logWarn('IX Bid Adapter: media type is not supported.');
return false;
}

Expand All @@ -1013,26 +1129,26 @@ export const spec = {
// since there is an ix bidder level size, make sure its valid
const ixSize = getFirstSize(paramsSize);
if (!ixSize) {
logError('ix bidder params: size has invalid format.');
logError('IX Bid Adapter: size has invalid format.', { bidder: BIDDER_CODE, code: ERROR_CODES.BID_SIZE_INVALID_FORMAT });
return false;
}
// check if the ix bidder level size, is present in ad unit level
if (!includesSize(bid.sizes, ixSize) &&
!(includesSize(mediaTypeVideoPlayerSize, ixSize)) &&
!(includesSize(mediaTypeBannerSizes, ixSize))) {
logError('ix bidder params: bid size is not included in ad unit sizes or player size.');
logError('IX Bid Adapter: bid size is not included in ad unit sizes or player size.', { bidder: BIDDER_CODE, code: ERROR_CODES.BID_SIZE_NOT_INCLUDED });
return false;
}
}

if (typeof bid.params.siteId !== 'string' && typeof bid.params.siteId !== 'number') {
logError('ix bidder params: siteId must be string or number value.');
logError('IX Bid Adapter: siteId must be string or number value.', { bidder: BIDDER_CODE, code: ERROR_CODES.SITE_ID_INVALID_VALUE });
return false;
}

if (hasBidFloor || hasBidFloorCur) {
if (!(hasBidFloor && hasBidFloorCur && isValidBidFloorParams(bid.params.bidFloor, bid.params.bidFloorCur))) {
logError('ix bidder params: bidFloor / bidFloorCur parameter has invalid format.');
logError('IX Bid Adapter: bidFloor / bidFloorCur parameter has invalid format.', { bidder: BIDDER_CODE, code: ERROR_CODES.BID_FLOOR_INVALID_FORMAT });
return false;
}
}
Expand All @@ -1041,7 +1157,7 @@ export const spec = {
const errorList = checkVideoParams(mediaTypeVideoRef, paramsVideoRef);
if (errorList.length) {
errorList.forEach((err) => {
logError(err);
logError(err, { bidder: BIDDER_CODE, code: ERROR_CODES.PROPERTY_NOT_INCLUDED });
});
return false;
}
Expand Down Expand Up @@ -1162,6 +1278,16 @@ export const spec = {

bids.push(bid);
}

if (deepAccess(requestBid, 'ext.ixdiag.err')) {
if (storage.localStorageIsEnabled()) {
try {
storage.removeDataFromLocalStorage(LOCAL_STORAGE_KEY);
} catch (e) {
logError('ix can not clear ixdiag from localStorage.');
}
}
}
}

return bids;
Expand Down
Loading