prebid/Prebid.js

View on GitHub
modules/ucfunnelBidAdapter.js

Summary

Maintainability
D
2 days
Test Coverage
import { generateUUID, _each, deepAccess } from '../src/utils.js';
import {registerBidder} from '../src/adapters/bidderFactory.js';
import {BANNER, VIDEO, NATIVE} from '../src/mediaTypes.js';
import { getStorageManager } from '../src/storageManager.js';
import { config } from '../src/config.js';
import { convertOrtbRequestToProprietaryNative } from '../src/native.js';

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

const COOKIE_NAME = 'ucf_uid';
const VER = 'ADGENT_PREBID-2018011501';
const BIDDER_CODE = 'ucfunnel';
const GVLID = 607;
const CURRENCY = 'USD';
const VIDEO_CONTEXT = {
  INSTREAM: 0,
  OUSTREAM: 2
}
const storage = getStorageManager({bidderCode: BIDDER_CODE});

export const spec = {
  code: BIDDER_CODE,
  gvlid: GVLID,
  ENDPOINT: 'https://hb.aralego.com/header',
  supportedMediaTypes: [BANNER, VIDEO, NATIVE],
  /**
   * Check if the bid is a valid zone ID in either number or string form
   * @param {object} bid the ucfunnel bid to validate
   * @return boolean for whether or not a bid is valid
   */
  isBidRequestValid: function(bid) {
    const isVideoMediaType = (bid.mediaTypes && bid.mediaTypes.video != null);
    const videoContext = (bid.mediaTypes && bid.mediaTypes.video != null) ? bid.mediaTypes.video.videoContext : '';

    if (typeof bid.params !== 'object' || typeof bid.params.adid != 'string') {
      return false;
    }

    if (isVideoMediaType && videoContext === 'outstream') {
      return false;
    }

    return true;
  },

  /**
   * @param {BidRequest[]} bidRequests
   * @param {*} bidderRequest
   * @return {ServerRequest}
   */
  buildRequests: function(bids, bidderRequest) {
    // convert Native ORTB definition to old-style prebid native definition
    bids = convertOrtbRequestToProprietaryNative(bids);

    return bids.map(bid => {
      return {
        method: 'GET',
        url: spec.ENDPOINT,
        data: getRequestData(bid, bidderRequest),
        bidRequest: bid
      }
    });
  },

  /**
   * Format ucfunnel responses as Prebid bid responses
   * @param {ucfunnelResponseObj} ucfunnelResponse A successful response from ucfunnel.
   * @return {Bid[]} An array of formatted bids.
   */
  interpretResponse: function (ucfunnelResponseObj, request) {
    const bidRequest = request.bidRequest;
    const ad = ucfunnelResponseObj ? ucfunnelResponseObj.body : {};

    let bid = {
      requestId: bidRequest.bidId,
      cpm: ad.cpm || 0,
      creativeId: ad.crid || ad.ad_id || bidRequest.params.adid,
      dealId: ad.deal || null,
      currency: ad.currency || 'USD',
      netRevenue: true,
      ttl: 1800,
      meta: {}
    };

    if (bidRequest.params && bidRequest.params.bidfloor && ad.cpm && ad.cpm < bidRequest.params.bidfloor) {
      bid.cpm = 0;
    }
    if (ad.creative_type) {
      bid.mediaType = ad.creative_type;
      bid.meta.mediaType = ad.creative_type;
    }
    if (ad.adomain) {
      bid.meta.advertiserDomains = ad.adomain;
    }

    switch (ad.creative_type) {
      case NATIVE:
        let nativeAd = ad.native;
        Object.assign(bid, {
          width: 1,
          height: 1,
          native: {
            title: nativeAd.title,
            body: nativeAd.desc,
            cta: nativeAd.ctatext,
            sponsoredBy: nativeAd.sponsored,
            image: nativeAd.image || nativeAd.image.url,
            icon: nativeAd.icon || nativeAd.icon.url,
            clickUrl: nativeAd.clickUrl,
            clickTrackers: (nativeAd.clicktrackers) ? nativeAd.clicktrackers : [],
            impressionTrackers: nativeAd.impressionTrackers,
          }
        });
        break;
      case VIDEO:
        Object.assign(bid, {
          vastUrl: ad.vastUrl,
          vastXml: ad.vastXml
        });

        if (bidRequest.sizes && bidRequest.sizes.length > 0) {
          Object.assign(bid, {
            width: bidRequest.sizes[0][0],
            height: bidRequest.sizes[0][1]
          });
        }
        break;
      case BANNER:
      default:
        var size = parseSizes(bidRequest);
        Object.assign(bid, {
          width: ad.width || size[0][0],
          height: ad.height || size[0][1],
          ad: ad.adm || ''
        });
    }

    return [bid];
  },

  getUserSyncs: function(syncOptions, serverResponses, gdprConsent = {}, uspConsent) {
    let gdprApplies = (gdprConsent && gdprConsent.gdprApplies) ? '1' : '';
    let apiVersion = (gdprConsent) ? gdprConsent.apiVersion : '';
    let consentString = (gdprConsent) ? gdprConsent.consentString : '';
    if (syncOptions.iframeEnabled) {
      return [{
        type: 'iframe',
        url: 'https://cdn.aralego.net/ucfad/cookie/sync.html' + getCookieSyncParameter(gdprApplies, apiVersion, consentString, uspConsent)
      }];
    } else if (syncOptions.pixelEnabled) {
      return [{
        type: 'image',
        url: 'https://sync.aralego.com/idSync' + getCookieSyncParameter(gdprApplies, apiVersion, consentString, uspConsent)
      }];
    }
  }
};
registerBidder(spec);

function getCookieSyncParameter(gdprApplies, apiVersion, consentString, uspConsent) {
  let param = '?';
  if (gdprApplies == '1') {
    param = param + 'gdpr=1&';
  }
  if (apiVersion == 1) {
    param = param + 'euconsent=' + consentString + '&';
  } else if (apiVersion == 2) {
    param = param + 'euconsent-v2=' + consentString + '&';
  }
  if (uspConsent) {
    param = param + 'usprivacy=' + uspConsent;
  }
  return (param == '?') ? '' : param;
}

function parseSizes(bid) {
  let params = bid.params;
  if (bid.mediaType === VIDEO) {
    let size = [];
    if (params.video && params.video.playerWidth && params.video.playerHeight) {
      size = [
        params.video.playerWidth,
        params.video.playerHeight
      ];
      return [size];
    }
  }
  return bid.sizes;
}

function getSupplyChain(schain) {
  var supplyChain = '';
  if (schain != null && schain.nodes) {
    supplyChain = schain.ver + ',' + schain.complete;
    for (let i = 0; i < schain.nodes.length; i++) {
      supplyChain += '!';
      supplyChain += (schain.nodes[i].asi) ? encodeURIComponent(schain.nodes[i].asi) : '';
      supplyChain += ',';
      supplyChain += (schain.nodes[i].sid) ? encodeURIComponent(schain.nodes[i].sid) : '';
      supplyChain += ',';
      supplyChain += (schain.nodes[i].hp) ? encodeURIComponent(schain.nodes[i].hp) : '';
      supplyChain += ',';
      supplyChain += (schain.nodes[i].rid) ? encodeURIComponent(schain.nodes[i].rid) : '';
      supplyChain += ',';
      supplyChain += (schain.nodes[i].name) ? encodeURIComponent(schain.nodes[i].name) : '';
      supplyChain += ',';
      supplyChain += (schain.nodes[i].domain) ? encodeURIComponent(schain.nodes[i].domain) : '';
    }
  }
  return supplyChain;
}

function getMediaType(mediaTypes) {
  if (mediaTypes != null && mediaTypes.banner) {
    return 'banner';
  } else if (mediaTypes != null && mediaTypes.video) {
    return 'video';
  } else if (mediaTypes != null && mediaTypes.native) {
    return 'native'
  }
  return 'banner';
}

function getFloor(bid, size, mediaTypes) {
  if (bid.params.bidfloor) {
    return bid.params.bidfloor;
  }
  if (typeof bid.getFloor === 'function') {
    var bidFloor = bid.getFloor({
      currency: CURRENCY,
      mediaType: getMediaType(mediaTypes),
      size: (size) ? [ size[0], size[1] ] : '*',
    });
    if (bidFloor.currency === CURRENCY) {
      return bidFloor.floor;
    }
  }
  return undefined;
}

function addBidData(bidData, key, value) {
  if (value) {
    bidData[key] = value;
  }
}

function getFormat(size) {
  let formatList = []
  for (var i = 0; i < size.length; i++) {
    formatList.push(size[i].join(','));
  }
  return (formatList.length > 0) ? formatList.join(';') : '';
}

function getRequestData(bid, bidderRequest) {
  const size = parseSizes(bid);
  const language = navigator.language;
  const dnt = (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0;
  const userIdTdid = (bid.userId && bid.userId.tdid) ? bid.userId.tdid : '';
  const supplyChain = getSupplyChain(bid.schain);
  const bidFloor = getFloor(bid, size, bid.mediaTypes);
  const gpid = deepAccess(bid, 'ortb2Imp.ext.gpid');
  // general bid data
  let bidData = {
    ver: VER,
    ifr: 0,
    bl: language,
    je: 1,
    dnt: dnt,
    adid: bid.params.adid,
    tdid: userIdTdid,
    schain: supplyChain
  };

  addBidData(bidData, 'fp', bidFloor);
  addBidData(bidData, 'gpid', gpid);
  addUserId(bidData, bid.userId);

  bidData.u = bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation;
  bidData.host = bidderRequest.refererInfo.domain;

  if (window.location.ancestorOrigins && window.location.ancestorOrigins.length > 0) {
    bidData.ao = window.location.ancestorOrigins[window.location.ancestorOrigins.length - 1];
  }

  if (storage.cookiesAreEnabled()) {
    let ucfUid = '';
    if (storage.getCookie(COOKIE_NAME) != undefined) {
      ucfUid = storage.getCookie(COOKIE_NAME);
      bidData.ucfUid = ucfUid;
    } else {
      ucfUid = generateUUID();
      bidData.ucfUid = ucfUid;
      storage.setCookie(COOKIE_NAME, ucfUid);
    }
  }

  if (size != undefined && size.length > 0 && size[0].length == 2) {
    bidData.w = size[0][0];
    bidData.h = size[0][1];
  }
  addBidData(bidData, 'format', getFormat(size));

  if (bidderRequest && bidderRequest.uspConsent) {
    Object.assign(bidData, {
      usprivacy: bidderRequest.uspConsent
    });
  }
  if (bid.mediaTypes && bid.mediaTypes.video != null) {
    const videoContext = bid.mediaTypes.video.context;
    switch (videoContext) {
      case 'outstream':
        bidData.atype = VIDEO_CONTEXT.OUSTREAM;
        break;
      case 'instream':
      default:
        bidData.atype = VIDEO_CONTEXT.INSTREAM;
        break;
    }
  }

  if (bidderRequest && bidderRequest.gdprConsent) {
    Object.assign(bidData, {
      gdpr: bidderRequest.gdprConsent.gdprApplies ? 1 : 0,
      'euconsent-v2': bidderRequest.gdprConsent.consentString
    });
  }

  if (config.getConfig('coppa')) {
    bidData.coppa = true;
  }

  return bidData;
}

function addUserId(bidData, userId) {
  bidData['eids'] = '';
  _each(userId, (userIdObjectOrValue, userIdProviderKey) => {
    switch (userIdProviderKey) {
      case 'hadronId':
        if (userIdObjectOrValue.hadronId) {
          bidData[userIdProviderKey + 'hadronId'] = userIdObjectOrValue.hadronId;
        }
        if (userIdObjectOrValue.auSeg) {
          bidData[userIdProviderKey + '_auSeg'] = userIdObjectOrValue.auSeg;
        }
        break;
      case 'parrableId':
        if (userIdObjectOrValue.eid) {
          bidData[userIdProviderKey + '_eid'] = userIdObjectOrValue.eid;
        }
        break;
      case 'id5id':
        if (userIdObjectOrValue.uid) {
          bidData[userIdProviderKey + '_uid'] = userIdObjectOrValue.uid;
        }
        if (userIdObjectOrValue.ext && userIdObjectOrValue.ext.linkType) {
          bidData[userIdProviderKey + '_linkType'] = userIdObjectOrValue.ext.linkType;
        }
        break;
      case 'uid2':
        if (userIdObjectOrValue.id) {
          bidData['eids'] = (bidData['eids'].length > 0)
            ? (bidData['eids'] + '!' + userIdProviderKey + ',' + userIdObjectOrValue.id)
            : (userIdProviderKey + ',' + userIdObjectOrValue.id);
        }
        break;
      case 'connectid':
        if (userIdObjectOrValue) {
          bidData['eids'] = (bidData['eids'].length > 0)
            ? (bidData['eids'] + '!verizonMediaId,' + userIdObjectOrValue)
            : ('verizonMediaId,' + userIdObjectOrValue);
        }
        break;
      default:
        bidData[userIdProviderKey] = userIdObjectOrValue;
        break;
    }
  });

  return bidData;
}