prebid/Prebid.js

View on GitHub
modules/adtelligentBidAdapter.js

Summary

Maintainability
F
4 days
Test Coverage
import {_map, deepAccess, flatten, isArray, parseSizesInput} from '../src/utils.js';
import {registerBidder} from '../src/adapters/bidderFactory.js';
import {ADPOD, BANNER, VIDEO} from '../src/mediaTypes.js';
import {config} from '../src/config.js';
import {Renderer} from '../src/Renderer.js';
import {find} from '../src/polyfill.js';
import {chunk} from '../libraries/chunk/chunk.js';

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

const subdomainSuffixes = ['', 1, 2];
const AUCTION_PATH = '/v2/auction/';
const PROTOCOL = 'https://';
const HOST_GETTERS = {
  default: (function () {
    let num = 0;
    return function () {
      return 'ghb' + subdomainSuffixes[num++ % subdomainSuffixes.length] + '.adtelligent.com';
    }
  }()),
  streamkey: () => 'ghb.hb.streamkey.net',
  janet: () => 'ghb.bidder.jmgads.com',
  ocm: () => 'ghb.cenarius.orangeclickmedia.com',
  '9dotsmedia': () => 'ghb.platform.audiodots.com',
  indicue: () => 'ghb.console.indicue.com',
}
const getUri = function (bidderCode) {
  let bidderWithoutSuffix = bidderCode.split('_')[0];
  let getter = HOST_GETTERS[bidderWithoutSuffix] || HOST_GETTERS['default'];
  return PROTOCOL + getter() + AUCTION_PATH
}
const OUTSTREAM_SRC = 'https://player.adtelligent.com/outstream-unit/2.01/outstream.min.js';
const BIDDER_CODE = 'adtelligent';
const OUTSTREAM = 'outstream';
const DISPLAY = 'display';
const syncsCache = {};

export const spec = {
  code: BIDDER_CODE,
  gvlid: 410,
  aliases: [
    'streamkey',
    'janet',
    { code: 'selectmedia', gvlid: 775 },
    { code: 'ocm', gvlid: 1148 },
    '9dotsmedia',
    'indicue',
  ],
  supportedMediaTypes: [VIDEO, BANNER],
  isBidRequestValid: function (bid) {
    return !!deepAccess(bid, 'params.aid');
  },
  getUserSyncs: function (syncOptions, serverResponses) {
    const syncs = [];

    function addSyncs(bid) {
      const uris = bid.cookieURLs;
      const types = bid.cookieURLSTypes || [];

      if (Array.isArray(uris)) {
        uris.forEach((uri, i) => {
          const type = types[i] || 'image';

          if ((!syncOptions.pixelEnabled && type === 'image') ||
            (!syncOptions.iframeEnabled && type === 'iframe') ||
            syncsCache[uri]) {
            return;
          }

          syncsCache[uri] = true;
          syncs.push({
            type: type,
            url: uri
          })
        })
      }
    }

    if (syncOptions.pixelEnabled || syncOptions.iframeEnabled) {
      isArray(serverResponses) && serverResponses.forEach((response) => {
        if (response.body) {
          if (isArray(response.body)) {
            response.body.forEach(b => {
              addSyncs(b);
            })
          } else {
            addSyncs(response.body)
          }
        }
      })
    }
    return syncs;
  },
  /**
   * Make a server request from the list of BidRequests
   * @param bidRequests
   * @param adapterRequest
   */
  buildRequests: function (bidRequests, adapterRequest) {
    const adapterSettings = config.getConfig(adapterRequest.bidderCode)
    const chunkSize = deepAccess(adapterSettings, 'chunkSize', 10);
    const { tag, bids } = bidToTag(bidRequests, adapterRequest);
    const bidChunks = chunk(bids, chunkSize);
    return _map(bidChunks, (bids) => {
      return {
        data: Object.assign({}, tag, { BidRequests: bids }),
        adapterRequest,
        method: 'POST',
        url: getUri(adapterRequest.bidderCode)
      };
    })
  },

  /**
   * Unpack the response from the server into a list of bids
   * @param serverResponse
   * @param adapterRequest
   * @return {Bid[]} An array of bids which were nested inside the server
   */
  interpretResponse: function (serverResponse, { adapterRequest }) {
    serverResponse = serverResponse.body;
    let bids = [];

    if (!isArray(serverResponse)) {
      return parseRTBResponse(serverResponse, adapterRequest);
    }

    serverResponse.forEach(serverBidResponse => {
      bids = flatten(bids, parseRTBResponse(serverBidResponse, adapterRequest));
    });

    return bids;
  },

};

function parseRTBResponse(serverResponse, adapterRequest) {
  const isEmptyResponse = !serverResponse || !isArray(serverResponse.bids);
  const bids = [];

  if (isEmptyResponse) {
    return bids;
  }

  serverResponse.bids.forEach(serverBid => {
    const request = find(adapterRequest.bids, (bidRequest) => {
      return bidRequest.bidId === serverBid.requestId;
    });

    if (serverBid.cpm !== 0 && request !== undefined) {
      const bid = createBid(serverBid, request);

      bids.push(bid);
    }
  });

  return bids;
}

function bidToTag(bidRequests, adapterRequest) {
  // start publisher env
  const tag = {
    // TODO: is 'page' the right value here?
    Domain: deepAccess(adapterRequest, 'refererInfo.page')
  };
  if (config.getConfig('coppa') === true) {
    tag.Coppa = 1;
  }
  if (deepAccess(adapterRequest, 'gdprConsent.gdprApplies')) {
    tag.GDPR = 1;
    tag.GDPRConsent = deepAccess(adapterRequest, 'gdprConsent.consentString');
  }
  if (deepAccess(adapterRequest, 'uspConsent')) {
    tag.USP = deepAccess(adapterRequest, 'uspConsent');
  }
  if (deepAccess(bidRequests[0], 'schain')) {
    tag.Schain = deepAccess(bidRequests[0], 'schain');
  }
  if (deepAccess(bidRequests[0], 'userId')) {
    tag.UserIds = deepAccess(bidRequests[0], 'userId');
  }
  if (deepAccess(bidRequests[0], 'userIdAsEids')) {
    tag.UserEids = deepAccess(bidRequests[0], 'userIdAsEids');
  }
  if (window.adtDmp && window.adtDmp.ready) {
    tag.DMPId = window.adtDmp.getUID();
  }

  if (adapterRequest.gppConsent) {
    tag.GPP = adapterRequest.gppConsent.gppString;
    tag.GPPSid = adapterRequest.gppConsent.applicableSections?.toString();
  } else if (adapterRequest.ortb2?.regs?.gpp) {
    tag.GPP = adapterRequest.ortb2.regs.gpp;
    tag.GPPSid = adapterRequest.ortb2.regs.gpp_sid;
  }

  // end publisher env
  const bids = [];

  for (let i = 0, length = bidRequests.length; i < length; i++) {
    const bid = prepareBidRequests(bidRequests[i]);
    bids.push(bid);
  }

  return { tag, bids };
}

/**
 * Parse mediaType
 * @param bidReq {object}
 * @returns {object}
 */
function prepareBidRequests(bidReq) {
  const mediaType = deepAccess(bidReq, 'mediaTypes.video') ? VIDEO : DISPLAY;
  const sizes = mediaType === VIDEO ? deepAccess(bidReq, 'mediaTypes.video.playerSize') : deepAccess(bidReq, 'mediaTypes.banner.sizes');
  const bidReqParams = {
    'CallbackId': bidReq.bidId,
    'Aid': bidReq.params.aid,
    'AdType': mediaType,
    'Sizes': parseSizesInput(sizes).join(',')
  };

  bidReqParams.PlacementId = bidReq.adUnitCode;
  if (bidReq.params.iframe) {
    bidReqParams.AdmType = 'iframe';
  }
  if (bidReq.params.vpb_placement_id) {
    bidReqParams.PlacementId = bidReq.params.vpb_placement_id;
  }
  if (mediaType === VIDEO) {
    const context = deepAccess(bidReq, 'mediaTypes.video.context');
    if (context === ADPOD) {
      bidReqParams.Adpod = deepAccess(bidReq, 'mediaTypes.video');
    }
  }
  return bidReqParams;
}

/**
 * Prepare all parameters for request
 * @param bidderRequest {object}
 * @returns {object}
 */
function getMediaType(bidderRequest) {
  return deepAccess(bidderRequest, 'mediaTypes.video') ? VIDEO : BANNER;
}

/**
 * Configure new bid by response
 * @param bidResponse {object}
 * @param bidRequest {Object}
 * @returns {object}
 */
function createBid(bidResponse, bidRequest) {
  const mediaType = getMediaType(bidRequest)
  const context = deepAccess(bidRequest, 'mediaTypes.video.context');
  const bid = {
    requestId: bidResponse.requestId,
    creativeId: bidResponse.cmpId,
    height: bidResponse.height,
    currency: bidResponse.cur,
    width: bidResponse.width,
    cpm: bidResponse.cpm,
    netRevenue: true,
    mediaType,
    ttl: 300,
    meta: {
      advertiserDomains: bidResponse.adomain || []
    }
  };

  if (mediaType === BANNER) {
    return Object.assign(bid, {
      ad: bidResponse.ad,
      adUrl: bidResponse.adUrl,
    });
  }
  if (context === ADPOD) {
    Object.assign(bid, {
      meta: {
        primaryCatId: bidResponse.primaryCatId,
      },
      video: {
        context: ADPOD,
        durationSeconds: bidResponse.durationSeconds
      }
    });
  }

  Object.assign(bid, {
    vastUrl: bidResponse.vastUrl
  });

  if (context === OUTSTREAM) {
    Object.assign(bid, {
      adResponse: bidResponse,
      renderer: newRenderer(bidResponse.requestId, bidRequest.params)
    });
  }

  return bid;
}

/**
 * Create Adtelligent renderer
 * @param requestId
 * @returns {*}
 */
function newRenderer(requestId, bidderParams) {
  const renderer = Renderer.install({
    id: requestId,
    url: OUTSTREAM_SRC,
    config: bidderParams.outstream || {},
    loaded: false
  });

  renderer.setRender(outstreamRender);

  return renderer;
}

/**
 * Initialise Adtelligent outstream
 * @param bid
 */
function outstreamRender(bid) {
  bid.renderer.push(() => {
    const opts = Object.assign({}, bid.renderer.getConfig(), {
      width: bid.width,
      height: bid.height,
      vastUrl: bid.vastUrl,
      elId: bid.adUnitCode
    });
    window.VOutstreamAPI.initOutstreams([opts]);
  });
}

registerBidder(spec);