prebid/Prebid.js

View on GitHub
modules/zmaticooBidAdapter.js

Summary

Maintainability
F
6 days
Test Coverage
import {deepAccess, isArray, isBoolean, isNumber, isStr, logWarn, triggerPixel} from '../src/utils.js';
import {registerBidder} from '../src/adapters/bidderFactory.js';
import {BANNER, VIDEO} from '../src/mediaTypes.js';

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

const BIDDER_CODE = 'zmaticoo';
const ENDPOINT_URL = 'https://bid.zmaticoo.com/prebid/bid';
const DEFAULT_CUR = 'USD';
const TTL = 200;
const NET_REV = true;

const DATA_TYPES = {
  'NUMBER': 'number', 'STRING': 'string', 'BOOLEAN': 'boolean', 'ARRAY': 'array', 'OBJECT': 'object'
};
const VIDEO_CUSTOM_PARAMS = {
  'mimes': DATA_TYPES.ARRAY,
  'minduration': DATA_TYPES.NUMBER,
  'maxduration': DATA_TYPES.NUMBER,
  'startdelay': DATA_TYPES.NUMBER,
  'playbackmethod': DATA_TYPES.ARRAY,
  'api': DATA_TYPES.ARRAY,
  'protocols': DATA_TYPES.ARRAY,
  'w': DATA_TYPES.NUMBER,
  'h': DATA_TYPES.NUMBER,
  'battr': DATA_TYPES.ARRAY,
  'linearity': DATA_TYPES.NUMBER,
  'placement': DATA_TYPES.NUMBER,
  'plcmt': DATA_TYPES.NUMBER,
  'minbitrate': DATA_TYPES.NUMBER,
  'maxbitrate': DATA_TYPES.NUMBER,
  'skip': DATA_TYPES.NUMBER
}

export const spec = {
  code: BIDDER_CODE,
  supportedMediaTypes: [BANNER, VIDEO],

  /**
   * Determines whether or not the given bid request is valid.
   *
   * @param {BidRequest} bid The bid params to validate.
   * @return boolean True if this is a valid bid, and false otherwise.
   */
  isBidRequestValid: function (bid) {
    // check for all required bid fields
    if (!(hasBannerMediaType(bid) || hasVideoMediaType(bid))) {
      logWarn('Invalid bid request - missing required mediaTypes');
      return false;
    }
    if (!(bid && bid.params)) {
      logWarn('Invalid bid request - missing required bid data');
      return false;
    }

    if (!(bid.params.pubId)) {
      logWarn('Invalid bid request - missing required field pubId');
      return false;
    }
    return true;
  },

  /**
   * Make a server request from the list of BidRequests.
   *
   * @param {Bids[]} validBidRequests - an array of bidRequest objects
   * @param {BidderRequest} bidderRequest - master bidRequest object
   * @return ServerRequest Info describing the request to the server.
   */
  buildRequests: function (validBidRequests, bidderRequest) {
    const secure = 1;
    const request = validBidRequests[0];
    const params = request.params;
    const imps = validBidRequests.map(request => {
      const impData = {
        id: request.bidId,
        secure: secure,
        ext: {
          bidder: {
            pubId: params.pubId
          }
        }
      };
      if (params.tagid) {
        impData.tagid = params.tagid;
      }
      if (request.mediaTypes) {
        for (const mediaType in request.mediaTypes) {
          switch (mediaType) {
            case BANNER:
              impData.banner = buildBanner(request);
              break;
            case VIDEO:
              impData.video = buildVideo(request);
              break;
          }
        }
      }
      if (typeof bidderRequest.getFloor === 'function') {
        const floorInfo = bidderRequest.getFloor({
          currency: 'USD',
          mediaType: impData.video ? 'video' : 'banner',
          size: [impData.video ? impData.video.w : impData.banner.w, impData.video ? impData.video.h : impData.banner.h]
        });
        if (floorInfo && floorInfo.floor) {
          impData.bidfloor = floorInfo.floor;
        }
      }
      if (!impData.bidfloor && params.bidfloor) {
        impData.bidfloor = params.bidfloor;
      }
      return impData;
    });
    let payload = {
      id: bidderRequest.bidderRequestId,
      imp: imps,
      site: params.site ? params.site : {},
      app: params.app ? params.app : {},
      device: params.device ? params.device : {},
      user: params.user ? params.user : {},
      at: params.at,
      tmax: params.tmax,
      wseat: params.wseat,
      bseat: params.bseat,
      allimps: params.allimps,
      cur: [DEFAULT_CUR],
      wlang: params.wlang,
      bcat: deepAccess(bidderRequest.ortb2Imp, 'bcat') || params.bcat,
      badv: params.badv,
      bapp: params.bapp,
      source: params.source ? params.source : {},
      regs: params.regs ? params.regs : {},
      ext: params.ext ? params.ext : {}
    };
    payload.regs.ext = {}
    payload.user.ext = {}
    payload.device.ua = navigator.userAgent;
    payload.device.ip = navigator.ip;
    payload.site.page = bidderRequest?.refererInfo?.page || window.location.href;
    payload.site.domain = _getDomainFromURL(payload.site.page);
    payload.site.mobile = /(ios|ipod|ipad|iphone|android)/i.test(navigator.userAgent) ? 1 : 0;
    if (params.test) {
      payload.test = params.test;
    }
    if (bidderRequest.gdprConsent) {
      payload.regs.ext = Object.assign(payload.regs.ext, {gdpr: bidderRequest.gdprConsent.gdprApplies == true ? 1 : 0});
    }
    if (bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) {
      payload.user.ext = Object.assign(payload.user.ext, {consent: bidderRequest.gdprConsent.consentString});
    }
    const postUrl = ENDPOINT_URL;
    return {
      method: 'POST', url: postUrl, data: JSON.stringify(payload),
    };
  },

  /**
   * Unpack the response from the server into a list of bids.
   *
   * @param {ServerResponse} serverResponse A successful response from the server.
   * @param {BidRequest} bidRequest The payload from the server's response.
   * @return {Bid[]} An array of bids which were nested inside the server.
   */
  interpretResponse: function (serverResponse, bidRequest) {
    let bidResponses = [];
    const response = (serverResponse || {}).body;
    if (response && response.seatbid && response.seatbid.length && response.seatbid[0].bid && response.seatbid[0].bid.length) {
      response.seatbid.forEach(zmSeatbid => {
        zmSeatbid.bid.forEach(zmBid => {
          let bid = {
            requestId: zmBid.impid,
            cpm: zmBid.price,
            currency: response.cur,
            width: zmBid.w,
            height: zmBid.h,
            ad: zmBid.adm,
            ttl: TTL,
            creativeId: zmBid.crid,
            netRevenue: NET_REV,
            nurl: zmBid.nurl,
          };
          bid.meta = {
            advertiserDomains: (zmBid.adomain && zmBid.adomain.length) ? zmBid.adomain : []
          };
          if (zmBid.ext && zmBid.ext.vast_url) {
            bid.vastXml = zmBid.ext.vast_url;
          }
          if (zmBid.ext && zmBid.ext.prebid) {
            bid.mediaType = zmBid.ext.prebid.type
          } else {
            bid.mediaType = BANNER
          }
          bidResponses.push(bid);
        })
      })
    }
    return bidResponses;
  },
  onBidWon: function (bid) {
    if (!bid['nurl']) {
      return false
    }
    const winCpm = (bid.hasOwnProperty('originalCpm')) ? bid.originalCpm : bid.cpm
    const winCurr = (bid.hasOwnProperty('originalCurrency') && bid.hasOwnProperty('originalCpm')) ? bid.originalCurrency : bid.currency
    const winUrl = bid.nurl.replace(
      /\$\{AUCTION_PRICE\}/,
      winCpm
    ).replace(
      /\$\{AUCTION_IMP_ID\}/,
      bid.requestId
    ).replace(
      /\$\{AUCTION_CURRENCY\}/,
      winCurr
    ).replace(
      /\$\{AUCTON_BID_ID\}/,
      bid.bidId
    ).replace(
      /\$\{AUCTION_ID\}/,
      bid.auctionId
    )
    triggerPixel(winUrl);
    return true
  }
}

function buildBanner(request) {
  let sizes = request.sizes;
  if (request.mediaTypes && request.mediaTypes.banner && request.mediaTypes.banner.sizes) {
    sizes = request.mediaTypes.banner.sizes;
  }
  return {
    w: sizes[0][0], h: sizes[0][1]
  };
}

function buildVideo(request) {
  let video = {};
  const videoParams = deepAccess(request, 'mediaTypes.video', {});
  for (const key in VIDEO_CUSTOM_PARAMS) {
    if (videoParams.hasOwnProperty(key)) {
      video[key] = checkParamDataType(key, videoParams[key], VIDEO_CUSTOM_PARAMS[key]);
    }
  }
  if (videoParams.playerSize) {
    if (isArray(videoParams.playerSize[0])) {
      video.w = parseInt(videoParams.playerSize[0][0], 10);
      video.h = parseInt(videoParams.playerSize[0][1], 10);
    } else if (isNumber(videoParams.playerSize[0])) {
      video.w = parseInt(videoParams.playerSize[0], 10);
      video.h = parseInt(videoParams.playerSize[1], 10);
    }
  }
  return video;
}

export function checkParamDataType(key, value, datatype) {
  let functionToExecute;
  switch (datatype) {
    case DATA_TYPES.BOOLEAN:
      functionToExecute = isBoolean;
      break;
    case DATA_TYPES.NUMBER:
      functionToExecute = isNumber;
      break;
    case DATA_TYPES.STRING:
      functionToExecute = isStr;
      break;
    case DATA_TYPES.ARRAY:
      functionToExecute = isArray;
      break;
  }
  if (functionToExecute(value)) {
    return value;
  }
  logWarn('Ignoring param key: ' + key + ', expects ' + datatype + ', found ' + typeof value);
  return undefined;
}

function hasBannerMediaType(bidRequest) {
  return !!deepAccess(bidRequest, 'mediaTypes.banner');
}

/**
 * @param {BidRequest} bidRequest bid request
 */
function hasVideoMediaType(bidRequest) {
  return !!deepAccess(bidRequest, 'mediaTypes.video');
}

export function _getDomainFromURL(url) {
  let anchor = document.createElement('a');
  anchor.href = url;
  return anchor.hostname;
}

registerBidder(spec);