prebid/Prebid.js

View on GitHub
modules/carodaBidAdapter.js

Summary

Maintainability
D
2 days
Test Coverage
// jshint esversion: 6, es3: false, node: true
'use strict'

import { registerBidder } from '../src/adapters/bidderFactory.js';
import { BANNER, VIDEO } from '../src/mediaTypes.js';
import {
  deepAccess,
  deepSetValue,
  logError,
  mergeDeep,
  parseSizesInput
} from '../src/utils.js';
import { config } from '../src/config.js';

const { getConfig } = config;

const BIDDER_CODE = 'caroda';
const GVLID = 954;

// some state info is required to synchronize with Caroda ad server
const topUsableWindow = getTopUsableWindow();

export const spec = {
  code: BIDDER_CODE,
  gvlid: GVLID,
  supportedMediaTypes: [BANNER, VIDEO],
  isBidRequestValid: bid => {
    const params = bid.params || {};
    const { ctok, placementId, priceType } = params;
    return typeof ctok === 'string' && (
      typeof placementId === 'string' ||
      typeof placementId === 'undefined'
    ) && (
      typeof priceType === 'undefined' ||
      priceType === 'gross' ||
      priceType === 'net'
    );
  },
  buildRequests: (validBidRequests, bidderRequest) => {
    topUsableWindow.carodaPageViewId = topUsableWindow.carodaPageViewId || Math.floor(Math.random() * 1e9);
    const pageViewId = topUsableWindow.carodaPageViewId;
    const ortbCommon = getORTBCommon(bidderRequest);
    const priceType =
      getFirstWithKey(validBidRequests, 'params.priceType') ||
      'net';
    const test = getFirstWithKey(validBidRequests, 'params.test');
    const currency = getConfig('currency.adServerCurrency');
    const eids = getFirstWithKey(validBidRequests, 'userIdAsEids');
    const schain = getFirstWithKey(validBidRequests, 'schain');
    const request = {
      // TODO: fix auctionId leak: https://github.com/prebid/Prebid.js/issues/9781
      auctionId: bidderRequest.auctionId,
      currency,
      hb_version: '$prebid.version$',
      ...ortbCommon,
      price_type: priceType
    };
    if (test) {
      request.test = 1;
    }
    if (schain) {
      request.schain = schain;
    }
    if (config.getConfig('coppa')) {
      deepSetValue(request, 'privacy.coppa', 1);
    }
    if (deepAccess(bidderRequest, 'gdprConsent.gdprApplies') !== undefined) {
      deepSetValue(
        request,
        'privacy.gdpr_consent',
        bidderRequest.gdprConsent.consentString
      );
      deepSetValue(
        request,
        'privacy.gdpr',
        bidderRequest.gdprConsent.gdprApplies & 1
      );
    }
    if (bidderRequest.uspConsent) {
      deepSetValue(request, 'privacy.us_privacy', bidderRequest.uspConsent);
    }
    if (eids) {
      deepSetValue(request, 'user.eids', eids);
    }
    return getImps(validBidRequests, request).map(imp => ({
      method: 'POST',
      url: 'https://prebid.caroda.io/api/hb?entry_id=' + pageViewId,
      data: JSON.stringify(imp)
    }));
  },
  interpretResponse: (serverResponse) => {
    if (!serverResponse.body) {
      return;
    }
    const { ok, error } = serverResponse.body
    if (error) {
      logError(BIDDER_CODE, ': server caught', error.message);
      return;
    }
    try {
      return JSON.parse(ok.value)
        .map((bid) => {
          const ret = {
            requestId: bid.bid_id,
            cpm: bid.cpm,
            creativeId: bid.creative_id,
            ttl: 300,
            netRevenue: true,
            currency: bid.currency,
            width: bid.w,
            height: bid.h,
            meta: {
              advertiserDomains: bid.adomain || []
            },
            ad: bid.ad,
            placementId: bid.placement_id
          }
          if (bid.adserver_targeting) {
            ret.adserverTargeting = bid.adserver_targeting
          }
          return ret
        })
        .filter(Boolean);
    } catch (e) {
      logError(BIDDER_CODE, ': caught', e);
    }
  }
}

registerBidder(spec)

function getFirstWithKey (collection, key) {
  for (let i = 0, result; i < collection.length; i++) {
    result = deepAccess(collection[i], key);
    if (result) {
      return result;
    }
  }
}

function getTopUsableWindow () {
  let res = window;
  try {
    while (window.top !== res && res.parent.location.href.length) {
      res = res.parent;
    }
  } catch (e) {}
  return res;
}

function getORTBCommon (bidderRequest) {
  let app, site;
  const commonFpd = bidderRequest.ortb2 || {};
  let { user } = commonFpd;
  if (typeof getConfig('app') === 'object') {
    app = getConfig('app') || {}
    if (commonFpd.app) {
      mergeDeep(app, commonFpd.app);
    }
  } else {
    site = getConfig('site') || {};
    if (commonFpd.site) {
      mergeDeep(site, commonFpd.site);
    }
    if (!site.page) {
      site.page = bidderRequest.refererInfo.page;
    }
  }
  const device = getConfig('device') || {};
  device.w = device.w || window.innerWidth;
  device.h = device.h || window.innerHeight;
  device.ua = device.ua || navigator.userAgent;
  return {
    app,
    site,
    user,
    device
  };
}

function getImps (validBidRequests, common) {
  return validBidRequests.map((bid) => {
    const floorInfo = bid.getFloor
      ? bid.getFloor({ currency: common.currency || 'EUR' })
      : {};
    const bidfloor = floorInfo.floor;
    const bidfloorcur = floorInfo.currency;
    const { ctok, placementId } = bid.params;
    const imp = {
      bid_id: bid.bidId,
      ctok,
      bidfloor,
      bidfloorcur,
      ...common
    };
    const bannerParams = deepAccess(bid, 'mediaTypes.banner');
    if (bannerParams && bannerParams.sizes) {
      const sizes = parseSizesInput(bannerParams.sizes);
      const format = sizes.map(size => {
        const [width, height] = size.split('x');
        const w = parseInt(width, 10);
        const h = parseInt(height, 10);
        return { w, h };
      });
      imp.banner = {
        format
      };
    }
    if (placementId) {
      imp.placement_id = placementId;
    }
    const videoParams = deepAccess(bid, 'mediaTypes.video');
    if (videoParams) {
      imp.video = videoParams;
    }
    return imp;
  })
}