prebid/Prebid.js

View on GitHub
modules/getintentBidAdapter.js

Summary

Maintainability
B
5 hrs
Test Coverage
import {getBidIdParameter, isFn, isInteger} from '../src/utils.js';
import { registerBidder } from '../src/adapters/bidderFactory.js';

/**
 * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest
 * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid
 */

const BIDDER_CODE = 'getintent';
const IS_NET_REVENUE = true;
const BID_HOST = 'px.adhigh.net';
const BID_BANNER_PATH = '/rtb/direct_banner';
const BID_VIDEO_PATH = '/rtb/direct_vast';
const BID_RESPONSE_TTL_SEC = 360;
const FLOOR_PARAM = 'floor';
const CURRENCY_PARAM = 'cur';
const DEFAULT_CURRENCY = 'RUB';
const VIDEO_PROPERTIES = {
  'protocols': 'protocols',
  'mimes': 'mimes',
  'min_dur': 'minduration',
  'max_dur': 'maxduration',
  'min_btr': 'minbitrate',
  'max_btr': 'maxbitrate',
  'vi_format': null,
  'api': 'api',
  'skippable': 'skip',
};
const SKIPPABLE_ALLOW = 'ALLOW';
const SKIPPABLE_NOT_ALLOW = 'NOT_ALLOW';

const OPTIONAL_PROPERTIES = [
  'sid'
];

export const spec = {
  code: BIDDER_CODE,
  aliases: ['getintentAdapter'],
  supportedMediaTypes: ['video', 'banner'],

  /**
   * Determines whether or not the given bid request is valid.
   *
   * @param {BidRequest} bid The bid to validate.
   * @return {boolean} True if this is a valid bid, and false otherwise.
   */
  isBidRequestValid: function(bid) {
    return !!(bid && bid.params && bid.params.pid && bid.params.tid);
  },

  /**
   * Make a server request from the list of BidRequests.
   *
   * @param {BidRequest[]} bidRequests - an array of bids.
   * @return ServerRequest[]
   */
  buildRequests: function(bidRequests) {
    return bidRequests.map(bidRequest => {
      let giBidRequest = buildGiBidRequest(bidRequest);
      return {
        method: 'GET',
        url: buildUrl(giBidRequest),
        data: giBidRequest,
      };
    });
  },

  /**
   * Callback for bids, after the call to DSP completes.
   * Parse the response from the server into a list of bids.
   *
   * @param {object} serverResponse A response from the GetIntent's server.
   * @return {Bid[]} An array of bids which were nested inside the server.
   */
  interpretResponse: function(serverResponse) {
    let responseBody = serverResponse.body;
    const bids = [];
    if (responseBody && responseBody.no_bid !== 1) {
      let size = parseSize(responseBody.size);
      let bid = {
        requestId: responseBody.bid_id,
        ttl: BID_RESPONSE_TTL_SEC,
        netRevenue: IS_NET_REVENUE,
        currency: responseBody.currency,
        creativeId: responseBody.creative_id,
        cpm: responseBody.cpm,
        width: size[0],
        height: size[1],
        meta: {
          advertiserDomains: responseBody.adomain || [],
        }
      };
      if (responseBody.vast_url) {
        bid.mediaType = 'video';
        bid.vastUrl = responseBody.vast_url;
      } else {
        bid.mediaType = 'banner';
        bid.ad = responseBody.ad;
      }
      bids.push(bid);
    }
    return bids;
  }

};

function buildUrl(bid) {
  return 'https://' + BID_HOST + (bid.is_video ? BID_VIDEO_PATH : BID_BANNER_PATH);
}

/**
 * Builds GI bid request from BidRequest.
 *
 * @param {BidRequest} bidRequest
 * @return {object} GI bid request
 */
function buildGiBidRequest(bidRequest) {
  let giBidRequest = {
    bid_id: bidRequest.bidId,
    pid: bidRequest.params.pid, // required
    tid: bidRequest.params.tid, // required
    known: bidRequest.params.known || 1,
    is_video: bidRequest.mediaType === 'video',
    resp_type: 'JSON',
    provider: 'direct.prebidjs'
  };
  if (bidRequest.sizes) {
    giBidRequest.size = produceSize(bidRequest.sizes);
  }

  const currency = getBidIdParameter(CURRENCY_PARAM, bidRequest.params);
  const floorInfo = getBidFloor(bidRequest, currency);
  if (floorInfo.floor) {
    giBidRequest[FLOOR_PARAM] = floorInfo.floor;
  }
  if (floorInfo.currency) {
    giBidRequest[CURRENCY_PARAM] = floorInfo.currency;
  }

  if (giBidRequest.is_video) {
    addVideo(bidRequest.params.video, bidRequest.mediaTypes.video, giBidRequest);
  }
  addOptional(bidRequest.params, giBidRequest, OPTIONAL_PROPERTIES);
  return giBidRequest;
}

function getBidFloor(bidRequest, currency) {
  let floorInfo = {};

  if (isFn(bidRequest.getFloor)) {
    floorInfo = bidRequest.getFloor({
      currency: currency || DEFAULT_CURRENCY,
      mediaType: bidRequest.mediaType,
      size: bidRequest.sizes || '*'
    });
  }

  return {
    floor: floorInfo.floor || bidRequest.params[FLOOR_PARAM] || 0,
    currency: floorInfo.currency || currency || '',
  };
}

function addVideo(videoParams, mediaTypesVideoParams, giBidRequest) {
  videoParams = videoParams || {};
  mediaTypesVideoParams = mediaTypesVideoParams || {};

  for (let videoParam in VIDEO_PROPERTIES) {
    let paramValue;

    const mediaTypesVideoParam = VIDEO_PROPERTIES[videoParam];
    if (videoParams.hasOwnProperty(videoParam)) {
      paramValue = videoParams[videoParam];
    } else if (mediaTypesVideoParam !== null && mediaTypesVideoParams.hasOwnProperty(mediaTypesVideoParam)) {
      if (mediaTypesVideoParam === 'skip') {
        paramValue = mediaTypesVideoParams[mediaTypesVideoParam] === 1 ? SKIPPABLE_ALLOW : SKIPPABLE_NOT_ALLOW;
      } else {
        paramValue = mediaTypesVideoParams[mediaTypesVideoParam];
      }
    }

    if (typeof paramValue !== 'undefined') {
      giBidRequest[videoParam] = Array.isArray(paramValue) ? paramValue.join(',') : paramValue;
    }
  }
}

function addOptional(params, request, props) {
  for (let i = 0; i < props.length; i++) {
    if (params.hasOwnProperty(props[i])) {
      request[props[i]] = params[props[i]];
    }
  }
}

/**
 * @param {String} s The string representing a size (e.g. "300x250").
 * @return {Number[]} An array with two elements: [width, height] (e.g.: [300, 250]).
 */
function parseSize(s) {
  return s.split('x').map(Number);
}

/**
 * @param {Array} sizes An array of sizes/numbers to be joined into single string.
 *                      May be an array (e.g. [300, 250]) or array of arrays (e.g. [[300, 250], [640, 480]].
 * @return {String} The string with sizes, e.g. array of sizes [[50, 50], [80, 80]] becomes "50x50,80x80" string.
 */
function produceSize (sizes) {
  function sizeToStr(s) {
    if (Array.isArray(s) && s.length === 2 && isInteger(s[0]) && isInteger(s[1])) {
      return s.join('x');
    } else {
      throw "Malformed parameter 'sizes'";
    }
  }
  if (Array.isArray(sizes) && Array.isArray(sizes[0])) {
    return sizes.map(sizeToStr).join(',');
  } else {
    return sizeToStr(sizes);
  }
}

registerBidder(spec);