prebid/Prebid.js

View on GitHub
modules/lemmaDigitalBidAdapter.js

Summary

Maintainability
F
4 days
Test Coverage
import * as utils from '../src/utils.js';
import { config } from '../src/config.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').ServerResponse} ServerResponse
 * @typedef {import('../src/adapters/bidderFactory.js').SyncOptions} SyncOptions
 * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync
 * @typedef {import('../src/adapters/bidderFactory.js').validBidRequests} validBidRequests
 */

var BIDDER_CODE = 'lemmadigital';
var LOG_WARN_PREFIX = 'LEMMADIGITAL: ';
var ENDPOINT = 'https://bid.lemmadigital.com/lemma/servad';
var USER_SYNC = 'https://sync.lemmadigital.com/js/usersync.html?';
var DEFAULT_CURRENCY = 'USD';
var AUCTION_TYPE = 2;
var DEFAULT_TMAX = 300;
var DEFAULT_NET_REVENUE = false;
var DEFAULT_SECURE = 1;
var RESPONSE_TTL = 300;
var pubId = 0;
var adunitId = 0;

export var 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: (bid) => {
    if (!bid || !bid.params) {
      utils.logError(LOG_WARN_PREFIX, 'nil/empty bid object');
      return false;
    }
    if (!utils.isEmpty(bid.params.pubId) || !utils.isNumber(bid.params.pubId)) {
      utils.logWarn(LOG_WARN_PREFIX + 'Error: publisherId is mandatory and cannot be string. Call to OpenBid will not be sent for ad unit: ' + JSON.stringify(bid));
      return false;
    }
    if (!bid.params.adunitId) {
      utils.logWarn(LOG_WARN_PREFIX + 'Error: adUnitId is mandatory. Call to OpenBid will not be sent for ad unit: ' + JSON.stringify(bid));
      return false;
    }
    // video bid request validation
    if (bid.params.hasOwnProperty('video')) {
      if (!bid.params.video.hasOwnProperty('mimes') || !utils.isArray(bid.params.video.mimes) || bid.params.video.mimes.length === 0) {
        utils.logWarn(LOG_WARN_PREFIX + 'Error: For video ads, mimes is mandatory and must specify atlease 1 mime value. Call to OpenBid will not be sent for ad unit:' + JSON.stringify(bid));
        return false;
      }
    }
    return true;
  },

  /**
   * Make a server request from the list of BidRequests.
   *
   * @param {validBidRequests[]} - an array of bids
   * @return ServerRequest Info describing the request to the server.
   */
  buildRequests: (validBidRequests, bidderRequest) => {
    if (validBidRequests.length === 0) {
      return;
    }
    var refererInfo;
    if (bidderRequest && bidderRequest.refererInfo) {
      refererInfo = bidderRequest.refererInfo;
    }
    var conf = spec._setRefURL(refererInfo);
    const request = spec._createoRTBRequest(validBidRequests, conf);
    if (request && request.imp.length == 0) {
      return;
    }
    spec._setOtherParams(bidderRequest, request);
    const endPoint = spec._endPointURL(validBidRequests);
    return {
      method: 'POST',
      url: endPoint,
      data: JSON.stringify(request),
    };
  },

  /**
   * Unpack the response from the server into a list of bids.
   *
   * @param {ServerResponse} response A successful response from the server.
   * @return {Bid[]} An array of bids which were nested inside the server.
   */
  interpretResponse: (response, request) => {
    return spec._parseRTBResponse(request, response.body);
  },

  /**
   * Register the user sync pixels which should be dropped after the auction.
   * @param {SyncOptions} syncOptions Which user syncs are allowed?
   * @param {ServerResponse[]} serverResponses List of server's responses.
   * @return {UserSync[]} The user syncs which should be dropped.
   */
  getUserSyncs: (syncOptions, serverResponses) => {
    let syncurl = USER_SYNC + 'pid=' + pubId;
    if (syncOptions.iframeEnabled) {
      return [{
        type: 'iframe',
        url: syncurl
      }];
    } else {
      utils.logWarn(LOG_WARN_PREFIX + 'Please enable iframe based user sync.');
    }
  },

  /**
   * Generate UUID
   */
  _createUUID: () => {
    return new Date().getTime().toString();
  },

  /**
   * parse object
   */
  _parseJSON: function (rawPayload) {
    try {
      if (rawPayload) {
        return JSON.parse(rawPayload);
      }
    } catch (ex) {
      utils.logError(LOG_WARN_PREFIX, 'Exception: ', ex);
    }
    return null;
  },

  /**
   *
   * set referal url
   */
  _setRefURL: (refererInfo) => {
    var conf = {};
    conf.pageURL = (refererInfo && refererInfo.referer) ? refererInfo.referer : window.location.href;
    if (refererInfo && refererInfo.referer) {
      conf.refURL = refererInfo.referer;
    } else {
      conf.refURL = '';
    }
    return conf;
  },

  /**
   * set other params into oRTB request
   */
  _setOtherParams: (request, ortbRequest) => {
    var params = request && request.params ? request.params : null;
    if (params) {
      ortbRequest.tmax = params.tmax;
      ortbRequest.bcat = params.bcat;
    }
  },

  /**
   * create IAB standard OpenRTB bid request
   */
  _createoRTBRequest: (bidRequests, conf) => {
    var oRTBObject = {};
    try {
      oRTBObject = {
        id: spec._createUUID(),
        at: AUCTION_TYPE,
        tmax: DEFAULT_TMAX,
        cur: [DEFAULT_CURRENCY],
        imp: spec._getImpressionArray(bidRequests),
        user: {},
        ext: {}
      };
      var bid = bidRequests[0];

      var site = spec._getSiteObject(bid, conf);
      if (site) {
        oRTBObject.site = site;
        // add the content object from config in request
        if (typeof config.getConfig('content') === 'object') {
          oRTBObject.site.content = config.getConfig('content');
        }
      }
      var app = spec._getAppObject(bid);
      if (app) {
        oRTBObject.app = app;
        if (typeof oRTBObject.app.content !== 'object' && typeof config.getConfig('content') === 'object') {
          oRTBObject.app.content =
            config.getConfig('content') || undefined;
        }
      }
      var device = spec._getDeviceObject(bid);
      if (device) {
        oRTBObject.device = device;
      }
      var source = spec._getSourceObject(bid);
      if (source) {
        oRTBObject.source = source;
      }
      return oRTBObject;
    } catch (ex) {
      utils.logError(LOG_WARN_PREFIX, 'ERROR ', ex);
    }
  },

  /**
   * create impression array objects
   */
  _getImpressionArray: (request) => {
    var impArray = [];
    var map = request.map(bid => spec._getImpressionObject(bid));
    if (map) {
      map.forEach(o => {
        if (o) {
          impArray.push(o);
        }
      });
    }
    return impArray;
  },

  /**
   * create impression (single) object
   */
  _getImpressionObject: (bid) => {
    var impression = {};
    var bObj;
    var vObj;
    var sizes = bid.hasOwnProperty('sizes') ? bid.sizes : [];
    var mediaTypes = '';
    var format = [];
    var params = bid && bid.params ? bid.params : null;
    impression = {
      id: bid.bidId,
      tagid: params.adunitId ? params.adunitId.toString() : undefined,
      secure: DEFAULT_SECURE,
      bidfloorcur: params.currency ? params.currency : DEFAULT_CURRENCY
    };
    if (params.bidFloor) {
      impression.bidfloor = params.bidFloor;
    }
    if (bid.hasOwnProperty('mediaTypes')) {
      for (mediaTypes in bid.mediaTypes) {
        switch (mediaTypes) {
          case BANNER:
            bObj = spec._getBannerRequest(bid);
            if (bObj) {
              impression.banner = bObj;
            }
            break;
          case VIDEO:
            vObj = spec._getVideoRequest(bid);
            if (vObj) {
              impression.video = vObj;
            }
            break;
        }
      }
    } else {
      bObj = {
        pos: 0,
        w: sizes && sizes[0] ? sizes[0][0] : 0,
        h: sizes && sizes[0] ? sizes[0][1] : 0,
      };
      if (utils.isArray(sizes) && sizes.length > 1) {
        sizes = sizes.splice(1, sizes.length - 1);
        sizes.forEach(size => {
          format.push({
            w: size[0],
            h: size[1]
          });
        });
        bObj.format = format;
      }
      impression.banner = bObj;
    }
    spec._setFloor(impression, bid);
    return impression.hasOwnProperty(BANNER) ||
      impression.hasOwnProperty(VIDEO) ? impression : undefined;
  },

  /**
   * set bid floor
   */
  _setFloor: (impObj, bid) => {
    let bidFloor = -1;
    // get lowest floor from floorModule
    if (typeof bid.getFloor === 'function') {
      [BANNER, VIDEO].forEach(mediaType => {
        if (impObj.hasOwnProperty(mediaType)) {
          let floorInfo = bid.getFloor({ currency: impObj.bidfloorcur, mediaType: mediaType, size: '*' });
          if (typeof floorInfo === 'object' && floorInfo.currency === impObj.bidfloorcur && !isNaN(parseInt(floorInfo.floor))) {
            let mediaTypeFloor = parseFloat(floorInfo.floor);
            bidFloor = (bidFloor == -1 ? mediaTypeFloor : Math.min(mediaTypeFloor, bidFloor));
          }
        }
      });
    }
    // get highest from impObj.bidfllor and floor from floor module
    // as we are using Math.max, it is ok if we have not got any floor from floorModule, then value of bidFloor will be -1
    if (impObj.bidfloor) {
      bidFloor = Math.max(bidFloor, impObj.bidfloor);
    }

    // assign value only if bidFloor is > 0
    impObj.bidfloor = ((!isNaN(bidFloor) && bidFloor > 0) ? bidFloor : undefined);
  },

  /**
   * parse Open RTB response
   */
  _parseRTBResponse: (request, response) => {
    var bidResponses = [];
    try {
      if (response.seatbid) {
        var currency = response.curr || DEFAULT_CURRENCY;
        var seatbid = response.seatbid;
        seatbid.forEach(seatbidder => {
          var bidder = seatbidder.bid;
          bidder.forEach(bid => {
            var req = spec._parseJSON(request.data);
            var newBid = {
              requestId: bid.impid,
              cpm: parseFloat(bid.price).toFixed(2),
              width: bid.w,
              height: bid.h,
              creativeId: bid.crid,
              currency: currency,
              netRevenue: DEFAULT_NET_REVENUE,
              ttl: RESPONSE_TTL,
              referrer: req.site.ref,
              ad: bid.adm
            };
            if (bid.dealid) {
              newBid.dealId = bid.dealid;
            }
            if (req.imp && req.imp.length > 0) {
              req.imp.forEach(robj => {
                if (bid.impid === robj.id) {
                  spec._checkMediaType(bid.adm, newBid);
                  switch (newBid.mediaType) {
                    case BANNER:
                      break;
                    case VIDEO:
                      newBid.width = bid.hasOwnProperty('w') ? bid.w : robj.video.w;
                      newBid.height = bid.hasOwnProperty('h') ? bid.h : robj.video.h;
                      newBid.vastXml = bid.adm;
                      break;
                  }
                }
              });
            }
            bidResponses.push(newBid);
          });
        });
      }
    } catch (error) {
      utils.logError(LOG_WARN_PREFIX, 'ERROR ', error);
    }
    return bidResponses;
  },

  /**
   * get bid request api end point url
   */
  _endPointURL: (request) => {
    var params = request && request[0].params ? request[0].params : null;
    if (params) {
      pubId = params.pubId ? params.pubId : 0;
      adunitId = params.adunitId ? params.adunitId : 0;
      return ENDPOINT + '?pid=' + pubId + '&aid=' + adunitId;
    }
    return null;
  },

  /**
   * get domain name from url
   */
  _getDomain: (url) => {
    var a = document.createElement('a');
    a.setAttribute('href', url);
    return a.hostname;
  },

  /**
   * create the site object
   */
  _getSiteObject: (request, conf) => {
    var params = request && request.params ? request.params : null;
    if (params) {
      pubId = params.pubId ? params.pubId : '0';
      var siteId = params.siteId ? params.siteId : '0';
      var appParams = params.app;
      if (!appParams) {
        return {
          publisher: {
            id: pubId.toString()
          },
          domain: spec._getDomain(conf.pageURL),
          id: siteId.toString(),
          ref: conf.refURL,
          page: conf.pageURL,
          cat: params.category,
          pagecat: params.page_category
        };
      }
    }
    return null;
  },

  /**
   * create the app object
   */
  _getAppObject: (request) => {
    var params = request && request.params ? request.params : null;
    if (params) {
      pubId = params.pubId ? params.pubId : 0;
      var appParams = params.app;
      if (appParams) {
        return {
          publisher: {
            id: pubId.toString(),
          },
          id: appParams.id,
          name: appParams.name,
          bundle: appParams.bundle,
          storeurl: appParams.storeUrl,
          domain: appParams.domain,
          cat: appParams.cat || params.category,
          pagecat: appParams.pagecat || params.page_category
        };
      }
    }
    return null;
  },

  /**
   * create the device object
   */
  _getDeviceObject: (request) => {
    var params = request && request.params ? request.params : null;
    if (params) {
      return {
        dnt: utils.getDNT() ? 1 : 0,
        ua: navigator.userAgent,
        language: (navigator.language || navigator.browserLanguage || navigator.userLanguage || navigator.systemLanguage),
        w: (window.screen.width || window.innerWidth),
        h: (window.screen.height || window.innerHeigh),
        geo: {
          country: params.country,
          lat: params.latitude,
          lon: params.longitude,
          accuracy: params.accuracy,
          region: params.region,
          city: params.city,
          zip: params.zip
        },
        ip: params.ip,
        make: params.make,
        model: params.model,
        os: params.os,
        carrier: params.carrier,
        devicetype: params.device_type,
        ifa: params.ifa,
      };
    }
    return null;
  },

  /**
   * create source object
   */
  _getSourceObject: (request) => {
    var params = request && request.params ? request.params : null;
    if (params) {
      return {
        pchain: params.pchain,
        ext: {
          schain: request.schain
        },
      };
    }
    return null;
  },

  /**
   * get request ad sizes
   */
  _getSizes: (request) => {
    if (request && request.sizes && utils.isArray(request.sizes[0]) && request.sizes[0].length > 0) {
      return request.sizes[0];
    }
    return null;
  },

  /**
   * create the banner object
   */
  _getBannerRequest: (bid) => {
    var bObj;
    var adFormat = [];
    if (utils.deepAccess(bid, 'mediaTypes.banner')) {
      var params = bid ? bid.params : null;
      var bannerData = params && params.banner;
      var sizes = spec._getSizes(bid) || [];
      if (sizes && sizes.length == 0) {
        sizes = bid.mediaTypes.banner.sizes[0];
      }
      if (sizes && sizes.length > 0) {
        bObj = {};
        bObj.w = sizes[0];
        bObj.h = sizes[1];
        bObj.pos = 0;
        if (bannerData) {
          bObj = utils.deepClone(bannerData);
        }
        sizes = bid.mediaTypes.banner.sizes;
        if (sizes.length > 0) {
          adFormat = [];
          sizes.forEach(function (size) {
            if (size.length > 1) {
              adFormat.push({ w: size[0], h: size[1] });
            }
          });
          if (adFormat.length > 0) {
            bObj.format = adFormat;
          }
        }
      } else {
        utils.logWarn(LOG_WARN_PREFIX + 'Error: mediaTypes.banner.sizes missing for adunit: ' + bid.params.adunitId);
      }
    }
    return bObj;
  },

  /**
   * create the video object
   */
  _getVideoRequest: (bid) => {
    var vObj;
    if (utils.deepAccess(bid, 'mediaTypes.video')) {
      var params = bid ? bid.params : null;
      var videoData = utils.mergeDeep(utils.deepAccess(bid.mediaTypes, 'video'), params.video);
      var sizes = bid.mediaTypes.video ? bid.mediaTypes.video.playerSize : []
      if (sizes && sizes.length > 0) {
        vObj = {};
        if (videoData) {
          vObj = utils.deepClone(videoData);
        }
        vObj.w = sizes[0];
        vObj.h = sizes[1];
      } else {
        utils.logWarn(LOG_WARN_PREFIX + 'Error: mediaTypes.video.sizes missing for adunit: ' + bid.params.adunitId);
      }
    }
    return vObj;
  },

  /**
   * check media type
   */
  _checkMediaType: (adm, newBid) => {
    // Create a regex here to check the strings
    var videoRegex = new RegExp(/VAST.*version/);
    if (videoRegex.test(adm)) {
      newBid.mediaType = VIDEO;
    } else {
      newBid.mediaType = BANNER;
    }
  }
};

registerBidder(spec);