Skip to content

Commit

Permalink
IX Bid Adapter: capture errors in LS and send errors in request (preb…
Browse files Browse the repository at this point in the history
…id#7630)

* capture errors in LS and send errors in request

* fix linting
  • Loading branch information
umakajan authored and Chris Pabst committed Jan 10, 2022
1 parent 7672e8c commit 0d2ca93
Show file tree
Hide file tree
Showing 2 changed files with 359 additions and 20 deletions.
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 @@ -498,6 +511,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 @@ -576,7 +596,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 @@ -606,7 +626,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 @@ -713,7 +733,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 @@ -946,10 +966,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 @@ -966,7 +1075,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 @@ -1000,6 +1109,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 @@ -1009,6 +1124,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 @@ -1020,26 +1136,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 @@ -1048,7 +1164,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 @@ -1169,6 +1285,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

0 comments on commit 0d2ca93

Please sign in to comment.