prebid/Prebid.js

View on GitHub
modules/optimeraRtdProvider.js

Summary

Maintainability
A
1 hr
Test Coverage
/**
 * This module adds optimera provider to the real time data module
 * The {@link module:modules/realTimeData} module is required
 *
 * The module will fetch targeting values from the Optimera server
 * and apply the tageting to each ad request. These values are created
 * from the Optimera Mesaurement script which is installed on the
 * Publisher's site.
 *
 * @module modules/optimeraRtdProvider
 * @requires module:modules/realTimeData
 */

/**
 * @typedef {Object} ModuleParams
 * @property {string} clientID
 * @property {string} optimeraKeyName
 * @property {string} device
 * @property {string} apiVersion
 */

import { logInfo, logError } from '../src/utils.js';
import { submodule } from '../src/hook.js';
import { ajaxBuilder } from '../src/ajax.js';

/**
 * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule
 */

/** @type {ModuleParams} */
let _moduleParams = {};

/**
 * Default Optimera Key Name
 * This can default to hb_deal_optimera for publishers
 * who used the previous Optimera Bidder Adapter.
 * @type {string}
 */
export let optimeraKeyName = 'hb_deal_optimera';

/**
 * Optimera Score File Base URL.
 * This is the base URL for the data endpoint request to fetch
 * the targeting values.
 * @type {string}
 */
export const scoresBaseURL = {
  v0: 'https://dyv1bugovvq1g.cloudfront.net/',
  v1: 'https://v1.oapi26b.com/',
};

/**
 * Optimera Score File URL.
 * @type {string}
 */
export let scoresURL;

/**
 * Optimera Client ID.
 * @type {string}
 */
export let clientID;

/**
 * Optional device parameter.
 * @type {string}
 */
export let device = 'default';

/**
 * Optional apiVersion parameter.
 * @type {string}
 */
export let apiVersion = 'v0';

/**
 * Targeting object for all ad positions.
 * @type {string}
 */
export let optimeraTargeting = {};

/**
 * Flag to indicateo if a new score file should be fetched.
 * @type {string}
 */
export let fetchScoreFile = true;

/**
 * Make the request for the Score File.
 */
export function scoreFileRequest() {
  logInfo('Fetch Optimera score file.');
  const ajax = ajaxBuilder();
  ajax(scoresURL,
    {
      success: (res, req) => {
        if (req.status === 200) {
          try {
            setScores(res);
          } catch (err) {
            logError('Unable to parse Optimera Score File.', err);
          }
        } else if (req.status === 403) {
          logError('Unable to fetch the Optimera Score File - 403');
        }
      },
      error: () => {
        logError('Unable to fetch the Optimera Score File.');
      }
    });
}

/**
 * Apply the Optimera targeting to the ad slots.
 */
export function returnTargetingData(adUnits, config) {
  const targeting = {};
  try {
    adUnits.forEach((adUnit) => {
      if (optimeraTargeting[adUnit]) {
        targeting[adUnit] = {};
        targeting[adUnit][optimeraKeyName] = [optimeraTargeting[adUnit]];
      }
    });
  } catch (err) {
    logError('error', err);
  }
  logInfo('Apply Optimera targeting');
  return targeting;
}

/**
 * Fetch a new score file when an auction starts.
 * Only fetch the new file if a new score file is needed.
 */
export function onAuctionInit(auctionDetails, config, userConsent) {
  setScoresURL();
  if (fetchScoreFile) {
    scoreFileRequest();
  }
}

/**
 * Initialize the Module.
 * moduleConfig.params.apiVersion can be either v0 or v1.
 */
export function init(moduleConfig) {
  _moduleParams = moduleConfig.params;
  if (_moduleParams && _moduleParams.clientID) {
    clientID = _moduleParams.clientID;
    if (_moduleParams.optimeraKeyName) {
      optimeraKeyName = (_moduleParams.optimeraKeyName);
    }
    if (_moduleParams.device) {
      device = _moduleParams.device;
    }
    if (_moduleParams.apiVersion) {
      apiVersion = (_moduleParams.apiVersion.includes('v1', 'v0')) ? _moduleParams.apiVersion : 'v0';
    }
    setScoresURL();
    scoreFileRequest();
    return true;
  }
  if (!_moduleParams.clientID) {
    logError('Optimera clientID is missing in the Optimera RTD configuration.');
  }
  return false;
}

/**
 * Set the score file url.
 *
 * This fully-formed URL is for the data endpoint request to fetch
 * the targeting values. This is not a js library, rather JSON
 * which has the targeting values for the page.
 *
 * The score file url is based on the web page url. If the new score file URL
 * has been updated, set the fetchScoreFile flag to true to is can be fetched.
 *
 */
export function setScoresURL() {
  const optimeraHost = window.location.host;
  const optimeraPathName = window.location.pathname;
  const baseUrl = scoresBaseURL[apiVersion] ? scoresBaseURL[apiVersion] : scoresBaseURL.v0;
  let newScoresURL;

  if (apiVersion === 'v1') {
    newScoresURL = `${baseUrl}api/products/scores?c=${clientID}&h=${optimeraHost}&p=${optimeraPathName}&s=${device}`;
  } else {
    let encoded = encodeURIComponent(`${optimeraHost}${optimeraPathName}`)
      .replaceAll('%2F', '/')
      .replaceAll('%20', '+');

    newScoresURL = `${baseUrl}${clientID}/${encoded}.js`;
  }

  if (scoresURL !== newScoresURL) {
    scoresURL = newScoresURL;
    fetchScoreFile = true;
  } else {
    fetchScoreFile = false;
  }
}

/**
 * Set the scores for the device if given.
 * Add data and insights to the winddow.optimera object.
 *
 * @param {*} result
 * @returns {string} JSON string of Optimera Scores.
 */
export function setScores(result) {
  let scores = {};
  try {
    scores = JSON.parse(result);
    if (device !== 'default' && scores.device[device]) {
      scores = scores.device[device];
    }
    logInfo(scores);
    window.optimera = window.optimera || {};
    window.optimera.data = window.optimera.data || {};
    window.optimera.insights = window.optimera.insights || {};
    Object.keys(scores).map((key) => {
      if (key !== 'insights') {
        window.optimera.data[key] = scores[key];
      }
    });
    if (scores.insights) {
      window.optimera.insights = scores.insights;
    }
  } catch (e) {
    logError('Optimera score file could not be parsed.');
  }
  optimeraTargeting = scores;
}

/** @type {RtdSubmodule} */
export const optimeraSubmodule = {
  /**
   * used to link submodule with realTimeData
   * @type {string}
   */
  name: 'optimeraRTD',
  /**
   * get data when an auction starts
   * @function
   */
  onAuctionInitEvent: onAuctionInit,
  /**
   * get data and send back to realTimeData module
   * @function
   */
  getTargetingData: returnTargetingData,
  init,
};

/**
 * Register the Sub Module.
 */
function registerSubModule() {
  submodule('realTimeData', optimeraSubmodule);
}

registerSubModule();