prebid/Prebid.js

View on GitHub
modules/saambaaBidAdapter.js

Summary

Maintainability
F
1 wk
Test Coverage
// TODO: this adapter appears to have no tests

import {deepAccess, generateUUID, isEmpty, isFn, parseSizesInput, parseUrl} from '../src/utils.js';
import {config} from '../src/config.js';
import {registerBidder} from '../src/adapters/bidderFactory.js';
import {BANNER, VIDEO} from '../src/mediaTypes.js';
import {find, includes} from '../src/polyfill.js';

const ADAPTER_VERSION = '1.0';
const BIDDER_CODE = 'saambaa';

export const VIDEO_ENDPOINT = 'https://nep.advangelists.com/xp/get?pubid=';
export const BANNER_ENDPOINT = 'https://nep.advangelists.com/xp/get?pubid=';
export const OUTSTREAM_SRC = 'https://player-cdn.beachfrontmedia.com/playerapi/loader/outstream.js';
export const VIDEO_TARGETING = ['mimes', 'playbackmethod', 'maxduration', 'skip', 'playerSize', 'context'];
export const DEFAULT_MIMES = ['video/mp4', 'application/javascript'];

let pubid = '';

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

  isBidRequestValid(bidRequest) {
    if (typeof bidRequest != 'undefined') {
      if (bidRequest.bidder !== BIDDER_CODE && typeof bidRequest.params === 'undefined') { return false; }
      if (bidRequest === '' || bidRequest.params.placement === '' || bidRequest.params.pubid === '') { return false; }
      return true;
    } else { return false; }
  },

  buildRequests(bids, bidderRequest) {
    let requests = [];
    let videoBids = bids.filter(bid => isVideoBidValid(bid));
    let bannerBids = bids.filter(bid => isBannerBidValid(bid));
    videoBids.forEach(bid => {
      pubid = getVideoBidParam(bid, 'pubid');
      requests.push({
        method: 'POST',
        url: VIDEO_ENDPOINT + pubid,
        data: createVideoRequestData(bid, bidderRequest),
        bidRequest: bid
      });
    });

    bannerBids.forEach(bid => {
      pubid = getBannerBidParam(bid, 'pubid');

      requests.push({
        method: 'POST',
        url: BANNER_ENDPOINT + pubid,
        data: createBannerRequestData(bid, bidderRequest),
        bidRequest: bid
      });
    });
    return requests;
  },

  interpretResponse(serverResponse, {bidRequest}) {
    let response = serverResponse.body;
    if (response !== null && isEmpty(response) == false) {
      if (isVideoBid(bidRequest)) {
        let bidResponse = {
          requestId: response.id,
          cpm: response.seatbid[0].bid[0].price,
          width: response.seatbid[0].bid[0].w,
          height: response.seatbid[0].bid[0].h,
          ttl: response.seatbid[0].bid[0].ttl || 60,
          creativeId: response.seatbid[0].bid[0].crid,
          currency: response.cur,
          meta: { 'advertiserDomains': response.seatbid[0].bid[0].adomain },
          mediaType: VIDEO,
          netRevenue: true
        }

        if (response.seatbid[0].bid[0].adm) {
          bidResponse.vastXml = response.seatbid[0].bid[0].adm;
          bidResponse.adResponse = {
            content: response.seatbid[0].bid[0].adm
          };
        } else {
          bidResponse.vastUrl = response.seatbid[0].bid[0].nurl;
        }

        return bidResponse;
      } else {
        return {
          requestId: response.id,
          bidderCode: BIDDER_CODE,
          cpm: response.seatbid[0].bid[0].price,
          width: response.seatbid[0].bid[0].w,
          height: response.seatbid[0].bid[0].h,
          ad: response.seatbid[0].bid[0].adm,
          ttl: response.seatbid[0].bid[0].ttl || 60,
          creativeId: response.seatbid[0].bid[0].crid,
          currency: response.cur,
          meta: { 'advertiserDomains': response.seatbid[0].bid[0].adomain },
          mediaType: BANNER,
          netRevenue: true
        }
      }
    }
  }
};

function isBannerBid(bid) {
  return deepAccess(bid, 'mediaTypes.banner') || !isVideoBid(bid);
}

function isVideoBid(bid) {
  return deepAccess(bid, 'mediaTypes.video');
}

function getBannerBidFloor(bid) {
  let floorInfo = isFn(bid.getFloor) ? bid.getFloor({ currency: 'USD', mediaType: 'banner', size: '*' }) : {};
  return floorInfo.floor || getBannerBidParam(bid, 'bidfloor');
}

function getVideoBidFloor(bid) {
  let floorInfo = isFn(bid.getFloor) ? bid.getFloor({ currency: 'USD', mediaType: 'video', size: '*' }) : {};
  return floorInfo.floor || getVideoBidParam(bid, 'bidfloor');
}

function isVideoBidValid(bid) {
  return isVideoBid(bid) && getVideoBidParam(bid, 'pubid') && getVideoBidParam(bid, 'placement');
}

function isBannerBidValid(bid) {
  return isBannerBid(bid) && getBannerBidParam(bid, 'pubid') && getBannerBidParam(bid, 'placement');
}

function getVideoBidParam(bid, key) {
  return deepAccess(bid, 'params.video.' + key) || deepAccess(bid, 'params.' + key);
}

function getBannerBidParam(bid, key) {
  return deepAccess(bid, 'params.banner.' + key) || deepAccess(bid, 'params.' + key);
}

function isMobile() {
  return (/(ios|ipod|ipad|iphone|android)/i).test(navigator.userAgent);
}

function isConnectedTV() {
  return (/(smart[-]?tv|hbbtv|appletv|googletv|hdmi|netcast\.tv|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b)/i).test(navigator.userAgent);
}

function getDoNotTrack() {
  return navigator.doNotTrack === '1' || window.doNotTrack === '1' || navigator.msDoNoTrack === '1' || navigator.doNotTrack === 'yes';
}

function findAndFillParam(o, key, value) {
  try {
    if (typeof value === 'function') {
      o[key] = value();
    } else {
      o[key] = value;
    }
  } catch (ex) {}
}

function getOsVersion() {
  let clientStrings = [
    { s: 'Android', r: /Android/ },
    { s: 'iOS', r: /(iPhone|iPad|iPod)/ },
    { s: 'Mac OS X', r: /Mac OS X/ },
    { s: 'Mac OS', r: /(MacPPC|MacIntel|Mac_PowerPC|Macintosh)/ },
    { s: 'Linux', r: /(Linux|X11)/ },
    { s: 'Windows 10', r: /(Windows 10.0|Windows NT 10.0)/ },
    { s: 'Windows 8.1', r: /(Windows 8.1|Windows NT 6.3)/ },
    { s: 'Windows 8', r: /(Windows 8|Windows NT 6.2)/ },
    { s: 'Windows 7', r: /(Windows 7|Windows NT 6.1)/ },
    { s: 'Windows Vista', r: /Windows NT 6.0/ },
    { s: 'Windows Server 2003', r: /Windows NT 5.2/ },
    { s: 'Windows XP', r: /(Windows NT 5.1|Windows XP)/ },
    { s: 'UNIX', r: /UNIX/ },
    { s: 'Search Bot', r: /(nuhk|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask Jeeves\/Teoma|ia_archiver)/ }
  ];
  let cs = find(clientStrings, cs => cs.r.test(navigator.userAgent));
  return cs ? cs.s : 'unknown';
}

function getFirstSize(sizes) {
  return (sizes && sizes.length) ? sizes[0] : { w: undefined, h: undefined };
}

function parseSizes(sizes) {
  return parseSizesInput(sizes).map(size => {
    let [ width, height ] = size.split('x');
    return {
      w: parseInt(width, 10) || undefined,
      h: parseInt(height, 10) || undefined
    };
  });
}

function getVideoSizes(bid) {
  return parseSizes(deepAccess(bid, 'mediaTypes.video.playerSize') || bid.sizes);
}

function getBannerSizes(bid) {
  return parseSizes(deepAccess(bid, 'mediaTypes.banner.sizes') || bid.sizes);
}

function getTopWindowReferrer() {
  try {
    return window.top.document.referrer;
  } catch (e) {
    return '';
  }
}

function getVideoTargetingParams(bid) {
  const result = {};
  const excludeProps = ['playerSize', 'context', 'w', 'h'];
  Object.keys(Object(bid.mediaTypes.video))
    .filter(key => !includes(excludeProps, key))
    .forEach(key => {
      result[ key ] = bid.mediaTypes.video[ key ];
    });
  Object.keys(Object(bid.params.video))
    .filter(key => includes(VIDEO_TARGETING, key))
    .forEach(key => {
      result[ key ] = bid.params.video[ key ];
    });
  return result;
}

function createVideoRequestData(bid, bidderRequest) {
  let topLocation = getTopWindowLocation(bidderRequest);
  let topReferrer = getTopWindowReferrer();

  // if size is explicitly given via adapter params
  let paramSize = getVideoBidParam(bid, 'size');
  let sizes = [];
  let coppa = config.getConfig('coppa');

  if (typeof paramSize !== 'undefined' && paramSize != '') {
    sizes = parseSizes(paramSize);
  } else {
    sizes = getVideoSizes(bid);
  }
  const firstSize = getFirstSize(sizes);
  let floor = (getVideoBidFloor(bid) == null || typeof getVideoBidFloor(bid) == 'undefined') ? 0.5 : getVideoBidFloor(bid);
  let video = getVideoTargetingParams(bid);
  const o = {
    'device': {
      'langauge': (global.navigator.language).split('-')[0],
      'dnt': (global.navigator.doNotTrack === 1 ? 1 : 0),
      'devicetype': isMobile() ? 4 : isConnectedTV() ? 3 : 2,
      'js': 1,
      'os': getOsVersion()
    },
    'at': 2,
    'site': {},
    'tmax': 3000,
    'cur': ['USD'],
    'id': bid.bidId,
    'imp': [],
    'regs': {
      'ext': {
      }
    },
    'user': {
      'ext': {
      }
    }
  };

  o.site['page'] = topLocation.href;
  o.site['domain'] = topLocation.hostname;
  o.site['search'] = topLocation.search;
  o.site['domain'] = topLocation.hostname;
  o.site['ref'] = topReferrer;
  o.site['mobile'] = isMobile() ? 1 : 0;
  const secure = topLocation.protocol.indexOf('https') === 0 ? 1 : 0;

  o.device['dnt'] = getDoNotTrack() ? 1 : 0;

  findAndFillParam(o.site, 'name', function() {
    return global.top.document.title;
  });

  findAndFillParam(o.device, 'h', function() {
    return global.screen.height;
  });
  findAndFillParam(o.device, 'w', function() {
    return global.screen.width;
  });

  let placement = getVideoBidParam(bid, 'placement');

  for (let j = 0; j < sizes.length; j++) {
    o.imp.push({
      'id': '' + j,
      'displaymanager': '' + BIDDER_CODE,
      'displaymanagerver': '' + ADAPTER_VERSION,
      'tagId': placement,
      'bidfloor': floor,
      'bidfloorcur': 'USD',
      'secure': secure,
      'video': Object.assign({
        'id': generateUUID(),
        'pos': 0,
        'w': firstSize.w,
        'h': firstSize.h,
        'mimes': DEFAULT_MIMES
      }, video)

    });
  }
  if (coppa) {
    o.regs.ext = {'coppa': 1};
  }
  if (bidderRequest && bidderRequest.gdprConsent) {
    let { gdprApplies, consentString } = bidderRequest.gdprConsent;
    o.regs.ext = {'gdpr': gdprApplies ? 1 : 0};
    o.user.ext = {'consent': consentString};
  }

  return o;
}

function getTopWindowLocation(bidderRequest) {
  return parseUrl(bidderRequest?.refererInfo?.page || '', { decodeSearchAsString: true });
}

function createBannerRequestData(bid, bidderRequest) {
  let topLocation = getTopWindowLocation(bidderRequest);
  let topReferrer = getTopWindowReferrer();

  // if size is explicitly given via adapter params

  let paramSize = getBannerBidParam(bid, 'size');
  let sizes = [];
  let coppa = config.getConfig('coppa');
  if (typeof paramSize !== 'undefined' && paramSize != '') {
    sizes = parseSizes(paramSize);
  } else {
    sizes = getBannerSizes(bid);
  }

  let floor = (getBannerBidFloor(bid) == null || typeof getBannerBidFloor(bid) == 'undefined') ? 0.1 : getBannerBidFloor(bid);
  const o = {
    'device': {
      'langauge': (global.navigator.language).split('-')[0],
      'dnt': (global.navigator.doNotTrack === 1 ? 1 : 0),
      'devicetype': isMobile() ? 4 : isConnectedTV() ? 3 : 2,
      'js': 1
    },
    'at': 2,
    'site': {},
    'tmax': 3000,
    'cur': ['USD'],
    'id': bid.bidId,
    'imp': [],
    'regs': {
      'ext': {
      }
    },
    'user': {
      'ext': {
      }
    }
  };

  o.site['page'] = topLocation.href;
  o.site['domain'] = topLocation.hostname;
  o.site['search'] = topLocation.search;
  o.site['domain'] = topLocation.hostname;
  o.site['ref'] = topReferrer;
  o.site['mobile'] = isMobile() ? 1 : 0;
  const secure = topLocation.protocol.indexOf('https') === 0 ? 1 : 0;

  o.device['dnt'] = getDoNotTrack() ? 1 : 0;

  findAndFillParam(o.site, 'name', function() {
    return global.top.document.title;
  });

  findAndFillParam(o.device, 'h', function() {
    return global.screen.height;
  });
  findAndFillParam(o.device, 'w', function() {
    return global.screen.width;
  });

  let placement = getBannerBidParam(bid, 'placement');
  for (let j = 0; j < sizes.length; j++) {
    let size = sizes[j];

    o.imp.push({
      'id': '' + j,
      'displaymanager': '' + BIDDER_CODE,
      'displaymanagerver': '' + ADAPTER_VERSION,
      'tagId': placement,
      'bidfloor': floor,
      'bidfloorcur': 'USD',
      'secure': secure,
      'banner': {
        'id': generateUUID(),
        'pos': 0,
        'w': size['w'],
        'h': size['h']
      }
    });
  }
  if (coppa) {
    o.regs.ext = {'coppa': 1};
  }
  if (bidderRequest && bidderRequest.gdprConsent) {
    let { gdprApplies, consentString } = bidderRequest.gdprConsent;
    o.regs.ext = {'gdpr': gdprApplies ? 1 : 0};
    o.user.ext = {'consent': consentString};
  }

  return o;
}
registerBidder(spec);