UNPKG

mk9-prebid

Version:

Header Bidding Management Library

298 lines (254 loc) 9.19 kB
/** * This modules adds Publisher Common ID support to prebid.js. It's a simple numeric id * stored in the page's domain. When the module is included, an id is generated if needed, * persisted as a cookie, and automatically appended to all the bidRequest as bid.crumbs.pubcid. */ import * as utils from '../src/utils.js'; import { config } from '../src/config.js'; import events from '../src/events.js'; import CONSTANTS from '../src/constants.json'; import { getStorageManager } from '../src/storageManager.js'; const storage = getStorageManager(); const ID_NAME = '_pubcid'; const OPTOUT_NAME = '_pubcid_optout'; const DEFAULT_EXPIRES = 525600; // 1-year worth of minutes const PUB_COMMON = 'PublisherCommonId'; const EXP_SUFFIX = '_exp'; const COOKIE = 'cookie'; const LOCAL_STORAGE = 'html5'; let pubcidConfig = { enabled: true, interval: DEFAULT_EXPIRES, typeEnabled: LOCAL_STORAGE, create: true, extend: true, pixelUrl: '' }; /** * Set an item in the storage with expiry time. * @param {string} key Key of the item to be stored * @param {string} val Value of the item to be stored * @param {number} expires Expiry time in minutes */ export function setStorageItem(key, val, expires) { try { if (expires !== undefined && expires != null) { const expStr = (new Date(Date.now() + (expires * 60 * 1000))).toUTCString(); storage.setDataInLocalStorage(key + EXP_SUFFIX, expStr); } storage.setDataInLocalStorage(key, val); } catch (e) { utils.logMessage(e); } } /** * Retrieve an item from storage if it exists and hasn't expired. * @param {string} key Key of the item. * @returns {string|null} Value of the item. */ export function getStorageItem(key) { let val = null; try { const expVal = storage.getDataFromLocalStorage(key + EXP_SUFFIX); if (!expVal) { // If there is no expiry time, then just return the item val = storage.getDataFromLocalStorage(key); } else { // Only return the item if it hasn't expired yet. // Otherwise delete the item. const expDate = new Date(expVal); const isValid = (expDate.getTime() - Date.now()) > 0; if (isValid) { val = storage.getDataFromLocalStorage(key); } else { removeStorageItem(key); } } } catch (e) { utils.logMessage(e); } return val; } /** * Remove an item from storage * @param {string} key Key of the item to be removed */ export function removeStorageItem(key) { try { storage.removeDataFromLocalStorage(key + EXP_SUFFIX); storage.removeDataFromLocalStorage(key); } catch (e) { utils.logMessage(e); } } /** * Read a value either from cookie or local storage * @param {string} name Name of the item * @param {string} type storage type override * @returns {string|null} a string if item exists */ function readValue(name, type) { let value; if (!type) { type = pubcidConfig.typeEnabled; } if (type === COOKIE) { value = storage.getCookie(name); } else if (type === LOCAL_STORAGE) { value = getStorageItem(name); } if (value === 'undefined' || value === 'null') { return null; } return value; } /** * Write a value to either cookies or local storage * @param {string} name Name of the item * @param {string} value Value to be stored * @param {number} expInterval Expiry time in minutes */ function writeValue(name, value, expInterval) { if (name && value) { if (pubcidConfig.typeEnabled === COOKIE) { setCookie(name, value, expInterval, 'Lax'); } else if (pubcidConfig.typeEnabled === LOCAL_STORAGE) { setStorageItem(name, value, expInterval); } } } /** * Add a callback at end of auction to fetch a pixel * @param {string} pixelUrl Pixel URL * @param {string} id pubcid * @returns {boolean} True if callback is queued */ function queuePixelCallback(pixelUrl, id) { if (!pixelUrl) { return false; } id = id || ''; // Use pubcid as a cache buster const urlInfo = utils.parseUrl(pixelUrl); urlInfo.search.id = encodeURIComponent('pubcid:' + id); const targetUrl = utils.buildUrl(urlInfo); events.on(CONSTANTS.EVENTS.AUCTION_END, function auctionEndHandler() { events.off(CONSTANTS.EVENTS.AUCTION_END, auctionEndHandler); utils.triggerPixel(targetUrl); }); return true; } export function isPubcidEnabled() { return pubcidConfig.enabled; } export function getExpInterval() { return pubcidConfig.interval; } export function getPubcidConfig() { return pubcidConfig; } /** * Decorate ad units with pubcid. This hook function is called before the * real pbjs.requestBids is invoked, and can modify its parameter. The cookie is * not updated until this function is called. * @param {Object} config This is the same parameter as pbjs.requestBids, and config.adUnits will be updated. * @param {function} next The next function in the chain */ export function requestBidHook(next, config) { let adUnits = config.adUnits || $$PREBID_GLOBAL$$.adUnits; let pubcid = null; // Pass control to the next function if not enabled if (!pubcidConfig.enabled || !pubcidConfig.typeEnabled) { return next.call(this, config); } if (typeof window[PUB_COMMON] === 'object') { // If the page includes its own pubcid object, then use that instead. pubcid = window[PUB_COMMON].getId(); utils.logMessage(PUB_COMMON + ': pubcid = ' + pubcid); } else { // Otherwise get the existing cookie pubcid = readValue(ID_NAME); if (!pubcid) { if (pubcidConfig.create) { // Special handling for local storage to retain previously stored id in cookies if (pubcidConfig.typeEnabled === LOCAL_STORAGE) { pubcid = readValue(ID_NAME, COOKIE); } // Generate a new id if (!pubcid) { pubcid = utils.generateUUID(); } // Update the cookie/storage with the latest expiration date writeValue(ID_NAME, pubcid, pubcidConfig.interval); // Only return pubcid if it is saved successfully pubcid = readValue(ID_NAME); } queuePixelCallback(pubcidConfig.pixelUrl, pubcid); } else if (pubcidConfig.extend) { // Update the cookie/storage with the latest expiration date if (!queuePixelCallback(pubcidConfig.pixelUrl, pubcid)) { writeValue(ID_NAME, pubcid, pubcidConfig.interval); } } utils.logMessage('pbjs: pubcid = ' + pubcid); } // Append pubcid to each bid object, which will be incorporated // into bid requests later. if (adUnits && pubcid) { adUnits.forEach((unit) => { if (unit.bids && utils.isArray(unit.bids)) { unit.bids.forEach((bid) => { Object.assign(bid, {crumbs: {pubcid}}); }); } }); } return next.call(this, config); } // Helper to set a cookie export function setCookie(name, value, expires, sameSite) { let expTime = new Date(); expTime.setTime(expTime.getTime() + expires * 1000 * 60); storage.setCookie(name, value, expTime.toGMTString(), sameSite); } // Helper to read a cookie export function getCookie(name) { return storage.getCookie(name); } /** * Configuration function * @param {boolean} enable Enable or disable pubcid. By default the module is enabled. * @param {number} expInterval Expiration interval of the cookie in minutes. * @param {string} type Type of storage to use * @param {boolean} create Create the id if missing. Default is true. * @param {boolean} extend Extend the stored value when id is retrieved. Default is true. * @param {string} pixelUrl A pixel URL back to the publisher's own domain that may modify cookie attributes. */ export function setConfig({ enable, expInterval, type = 'html5,cookie', create, extend, pixelUrl } = {}) { if (enable !== undefined) { pubcidConfig.enabled = enable; } if (expInterval !== undefined) { pubcidConfig.interval = parseInt(expInterval, 10); } if (isNaN(pubcidConfig.interval)) { pubcidConfig.interval = DEFAULT_EXPIRES; } if (create !== undefined) { pubcidConfig.create = create; } if (extend !== undefined) { pubcidConfig.extend = extend; } if (pixelUrl !== undefined) { pubcidConfig.pixelUrl = pixelUrl; } // Default is to use local storage. Fall back to // cookie only if local storage is not supported. pubcidConfig.typeEnabled = null; const typeArray = type.split(','); for (let i = 0; i < typeArray.length; ++i) { const name = typeArray[i].trim(); if (name === COOKIE) { if (storage.cookiesAreEnabled()) { pubcidConfig.typeEnabled = COOKIE; break; } } else if (name === LOCAL_STORAGE) { if (storage.hasLocalStorage()) { pubcidConfig.typeEnabled = LOCAL_STORAGE; break; } } } } /** * Initialize module by 1) subscribe to configuration changes and 2) register hook */ export function initPubcid() { config.getConfig('pubcid', config => setConfig(config.pubcid)); const optout = (storage.cookiesAreEnabled() && readValue(OPTOUT_NAME, COOKIE)) || (storage.hasLocalStorage() && readValue(OPTOUT_NAME, LOCAL_STORAGE)); if (!optout) { $$PREBID_GLOBAL$$.requestBids.before(requestBidHook); } } initPubcid();