UNPKG

mk9-prebid

Version:

Header Bidding Management Library

670 lines (557 loc) 17.9 kB
import * as utils from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {getStorageManager} from '../src/storageManager.js'; const CONSTANTS = { BIDDER_CODE: 'invibes', BID_ENDPOINT: 'https://bid.videostep.com/Bid/VideoAdContent', SYNC_ENDPOINT: 'https://k.r66net.com/GetUserSync', TIME_TO_LIVE: 300, DEFAULT_CURRENCY: 'EUR', PREBID_VERSION: 6, METHOD: 'GET', INVIBES_VENDOR_ID: 436, USERID_PROVIDERS: ['pubcid', 'pubProvidedId', 'uid2', 'zeotapIdPlus', 'id5id'], META_TAXONOMY: ['networkId', 'networkName', 'agencyId', 'agencyName', 'advertiserId', 'advertiserName', 'advertiserDomains', 'brandId', 'brandName', 'primaryCatId', 'secondaryCatIds', 'mediaType'] }; const storage = getStorageManager(CONSTANTS.INVIBES_VENDOR_ID); export const spec = { code: CONSTANTS.BIDDER_CODE, gvlid: CONSTANTS.INVIBES_VENDOR_ID, /** * @param {object} bid * @return boolean */ isBidRequestValid: isBidRequestValid, /** * @param {BidRequest[]} bidRequests * @param bidderRequest * @return ServerRequest[] */ buildRequests: buildRequest, /** * @param {*} responseObj * @param {requestParams} bidRequests * @return {Bid[]} An array of bids which */ interpretResponse: function (responseObj, requestParams) { return handleResponse(responseObj, requestParams != null ? requestParams.bidRequests : null); }, getUserSyncs: function (syncOptions) { if (syncOptions.iframeEnabled) { const syncUrl = buildSyncUrl(); return { type: 'iframe', url: syncUrl }; } } }; registerBidder(spec); // some state info is required: cookie info, unique user visit id const topWin = getTopMostWindow(); let invibes = topWin.invibes = topWin.invibes || {}; invibes.purposes = invibes.purposes || [false, false, false, false, false, false, false, false, false, false]; invibes.legitimateInterests = invibes.legitimateInterests || [false, false, false, false, false, false, false, false, false, false]; let _customUserSync; function isBidRequestValid(bid) { if (invibes && typeof invibes.bidResponse === 'object') { utils.logInfo('Invibes Adapter - Bid response already received. Invibes only responds to one bid request per user visit'); return false; } if (typeof bid.params !== 'object') { return false; } let params = bid.params; if (params.placementId == null) { return false; } return true; } function buildRequest(bidRequests, bidderRequest) { bidderRequest = bidderRequest || {}; const _placementIds = []; let _loginId, _customEndpoint, _userId; let _ivAuctionStart = bidderRequest.auctionStart || Date.now(); bidRequests.forEach(function (bidRequest) { bidRequest.startTime = new Date().getTime(); _placementIds.push(bidRequest.params.placementId); _loginId = _loginId || bidRequest.params.loginId; _customEndpoint = _customEndpoint || bidRequest.params.customEndpoint; _customUserSync = _customUserSync || bidRequest.params.customUserSync; _userId = _userId || bidRequest.userId; }); invibes.optIn = invibes.optIn || readGdprConsent(bidderRequest.gdprConsent); invibes.visitId = invibes.visitId || generateRandomId(); invibes.noCookies = invibes.noCookies || invibes.getCookie('ivNoCookie'); let lid = initDomainId(invibes.domainOptions); const currentQueryStringParams = parseQueryStringParams(); let userIdModel = getUserIds(_userId); let bidParamsJson = { placementIds: _placementIds, loginId: _loginId, auctionStartTime: _ivAuctionStart, bidVersion: CONSTANTS.PREBID_VERSION }; if (userIdModel) { bidParamsJson.userId = userIdModel; } let data = { location: getDocumentLocation(topWin), videoAdHtmlId: generateRandomId(), showFallback: currentQueryStringParams['advs'] === '0', ivbsCampIdsLocal: invibes.getCookie('IvbsCampIdsLocal'), bidParamsJson: JSON.stringify(bidParamsJson), capCounts: getCappedCampaignsAsString(), vId: invibes.visitId, width: topWin.innerWidth, height: topWin.innerHeight, oi: invibes.optIn, kw: keywords, purposes: invibes.purposes.toString(), li: invibes.legitimateInterests.toString(), tc: invibes.gdpr_consent }; if (lid) { data.lId = lid; } const parametersToPassForward = 'videoaddebug,advs,bvci,bvid,istop,trybvid,trybvci'.split(','); for (let key in currentQueryStringParams) { if (currentQueryStringParams.hasOwnProperty(key)) { let value = currentQueryStringParams[key]; if (parametersToPassForward.indexOf(key) > -1 || /^vs|^invib/i.test(key)) { data[key] = value; } } } return { method: CONSTANTS.METHOD, url: _customEndpoint || CONSTANTS.BID_ENDPOINT, data: data, options: {withCredentials: true}, // for POST: { contentType: 'application/json', withCredentials: true } bidRequests: bidRequests }; } function handleResponse(responseObj, bidRequests) { if (bidRequests == null || bidRequests.length === 0) { utils.logInfo('Invibes Adapter - No bids have been requested'); return []; } if (!responseObj) { utils.logInfo('Invibes Adapter - Bid response is empty'); return []; } responseObj = responseObj.body || responseObj; responseObj = responseObj.videoAdContentResult || responseObj; if (typeof invibes.bidResponse === 'object') { utils.logInfo('Invibes Adapter - Bid response already received. Invibes only responds to one bid request per user visit'); return []; } invibes.bidResponse = responseObj; const bidResponses = []; for (let i = 0; i < bidRequests.length; i++) { let bidRequest = bidRequests[i]; let requestPlacement = null; if (responseObj.AdPlacements != null) { for (let j = 0; j < responseObj.AdPlacements.length; j++) { let bidModel = responseObj.AdPlacements[j].BidModel; if (bidModel != null && bidModel.PlacementId == bidRequest.params.placementId) { requestPlacement = responseObj.AdPlacements[j]; break; } } } else { let bidModel = responseObj.BidModel; if (bidModel != null && bidModel.PlacementId == bidRequest.params.placementId) { requestPlacement = responseObj; } } let bid = createBid(bidRequest, requestPlacement); if (bid !== null) { bidResponses.push(bid); } } return bidResponses; } function createBid(bidRequest, requestPlacement) { if (requestPlacement === null || requestPlacement.BidModel === null) { utils.logInfo('Invibes Adapter - Placement not configured for bidding ' + bidRequest.params.placementId); return null; } let bidModel = requestPlacement.BidModel; let ads = requestPlacement.Ads; if (!Array.isArray(ads) || ads.length < 1) { if (requestPlacement.AdReason != null) { utils.logInfo('Invibes Adapter - No ads ' + requestPlacement.AdReason); } utils.logInfo('Invibes Adapter - No ads available'); return null; } let ad = ads[0]; let size = getBiggerSize(bidRequest.sizes); const now = Date.now(); utils.logInfo('Bid auction started at ' + bidModel.AuctionStartTime + ' . Invibes registered the bid at ' + now + ' ; bid request took a total of ' + (now - bidModel.AuctionStartTime) + ' ms.'); return { requestId: bidRequest.bidId, cpm: ad.BidPrice, width: bidModel.Width || size[0], height: bidModel.Height || size[1], creativeId: ad.VideoExposedId, currency: bidModel.Currency || CONSTANTS.DEFAULT_CURRENCY, netRevenue: true, ttl: CONSTANTS.TIME_TO_LIVE, ad: renderCreative(bidModel), meta: addMeta(bidModel.Meta) }; } function addMeta(bidModelMeta) { var meta = {}; if (bidModelMeta != null) { for (let i = 0; i < CONSTANTS.META_TAXONOMY.length; i++) { if (bidModelMeta.hasOwnProperty(CONSTANTS.META_TAXONOMY[i])) { meta[CONSTANTS.META_TAXONOMY[i]] = bidModelMeta[CONSTANTS.META_TAXONOMY[i]]; } } } return meta; } function generateRandomId() { return (Math.round(Math.random() * 1e12)).toString(36).substring(0, 10); } function getDocumentLocation(topWin) { return topWin.location.href.substring(0, 300).split(/[?#]/)[0]; } function getUserIds(bidUserId) { let userId; if (bidUserId) { CONSTANTS.USERID_PROVIDERS.forEach(provider => { if (bidUserId[provider]) { userId = userId || {}; userId[provider] = bidUserId[provider]; } }); } return userId; } function parseQueryStringParams() { let params = {}; try { params = JSON.parse(localStorage.ivbs); } catch (e) { } let re = /[\\?&]([^=]+)=([^\\?&#]+)/g; let m; while ((m = re.exec(window.location.href)) != null) { if (m.index === re.lastIndex) { re.lastIndex++; } params[m[1].toLowerCase()] = m[2]; } return params; } function getBiggerSize(array) { let result = [0, 0]; for (let i = 0; i < array.length; i++) { if (array[i][0] * array[i][1] > result[0] * result[1]) { result = array[i]; } } return result; } function getTopMostWindow() { let res = window; try { while (top !== res) { if (res.parent.location.href.length) { res = res.parent; } } } catch (e) { } return res; } function renderCreative(bidModel) { return `<html> <head><script type='text/javascript'>inDapIF=true;</script></head> <body style='margin : 0; padding: 0;'> creativeHtml </body> </html>` .replace('creativeHtml', bidModel.CreativeHtml); } function getCappedCampaignsAsString() { const key = 'ivvcap'; if (!invibes.optIn || !invibes.purposes[0]) { return ''; } let loadData = function () { try { return JSON.parse(storage.getDataFromLocalStorage(key)) || {}; } catch (e) { return {}; } }; let saveData = function (data) { storage.setDataInLocalStorage(key, JSON.stringify(data)); }; let clearExpired = function () { let now = new Date().getTime(); let data = loadData(); let dirty = false; Object.keys(data).forEach(function (k) { let exp = data[k][1]; if (exp <= now) { delete data[k]; dirty = true; } }); if (dirty) { saveData(data); } }; let getCappedCampaigns = function () { clearExpired(); let data = loadData(); return Object.keys(data) .filter(function (k) { return data.hasOwnProperty(k); }) .sort() .map(function (k) { return [k, data[k][0]]; }); }; return getCappedCampaigns() .map(function (record) { return record.join('='); }) .join(','); } function buildSyncUrl() { let syncUrl = _customUserSync || CONSTANTS.SYNC_ENDPOINT; syncUrl += '?visitId=' + invibes.visitId; syncUrl += '&optIn=' + invibes.optIn; const did = invibes.getCookie('ivbsdid'); if (did) { syncUrl += '&ivbsdid=' + encodeURIComponent(did); } const bks = invibes.getCookie('ivvbks'); if (bks) { syncUrl += '&ivvbks=' + encodeURIComponent(bks); } return syncUrl; } function readGdprConsent(gdprConsent) { if (gdprConsent && gdprConsent.vendorData) { invibes.gdpr_consent = getVendorConsentData(gdprConsent.vendorData); if (!gdprConsent.vendorData.gdprApplies || gdprConsent.vendorData.hasGlobalConsent) { var index; for (index = 0; index < invibes.purposes.length; ++index) { invibes.purposes[index] = true; } for (index = 0; index < invibes.legitimateInterests.length; ++index) { invibes.legitimateInterests[index] = true; } return 2; } let purposeConsents = getPurposeConsents(gdprConsent.vendorData); if (purposeConsents == null) { return 0; } let purposesLength = getPurposeConsentsCounter(gdprConsent.vendorData); if (!tryCopyValueToArray(purposeConsents, invibes.purposes, purposesLength)) { return 0; } let legitimateInterests = getLegitimateInterests(gdprConsent.vendorData); tryCopyValueToArray(legitimateInterests, invibes.legitimateInterests, 10); let invibesVendorId = CONSTANTS.INVIBES_VENDOR_ID.toString(10); let vendorConsents = getVendorConsents(gdprConsent.vendorData); let vendorHasLegitimateInterest = getVendorLegitimateInterest(gdprConsent.vendorData)[invibesVendorId] === true; if (vendorConsents == null || vendorConsents[invibesVendorId] == null) { return 4; } if (vendorConsents[invibesVendorId] === false && vendorHasLegitimateInterest === false) { return 0; } return 2; } return 0; } function tryCopyValueToArray(value, target, length) { if (value instanceof Array) { for (let i = 0; i < length && i < value.length; i++) { target[i] = !((value[i] === false || value[i] === 'false' || value[i] == null)); } return true; } if (typeof value === 'object' && value !== null) { let i = 0; for (let prop in value) { if (i === length) { break; } if (value.hasOwnProperty(prop)) { let parsedProp = parseInt(prop); if (isNaN(parsedProp)) { target[i] = !((value[prop] === false || value[prop] === 'false' || value[prop] == null)); } else { target[parsedProp - 1] = !((value[prop] === false || value[prop] === 'false' || value[prop] == null)); } i++; } } return true; } return false; } function getPurposeConsentsCounter(vendorData) { if (vendorData.purpose && vendorData.purpose.consents) { return 10; } return 5; } function getPurposeConsents(vendorData) { if (vendorData.purpose && vendorData.purpose.consents) { return vendorData.purpose.consents; } if (vendorData.purposeConsents) { return vendorData.purposeConsents; } return null; } function getLegitimateInterests(vendorData) { if (vendorData.purpose && vendorData.purpose.legitimateInterests) { return vendorData.purpose.legitimateInterests; } return null; } function getVendorConsentData(vendorData) { if (vendorData.purpose && vendorData.purpose.consents) { if (vendorData.tcString != null) { return vendorData.tcString; } } return vendorData.consentData; }; function getVendorConsents(vendorData) { if (vendorData.vendor && vendorData.vendor.consents) { return vendorData.vendor.consents; } if (vendorData.vendorConsents) { return vendorData.vendorConsents; } return null; } function getVendorLegitimateInterest(vendorData) { if (vendorData.vendor && vendorData.vendor.legitimateInterests) { return vendorData.vendor.legitimateInterests; } return {}; } /// Local domain cookie management ===================== invibes.Uid = { generate: function () { let maxRand = parseInt('zzzzzz', 36) let mkRand = function () { return Math.floor(Math.random() * maxRand).toString(36); }; let rand1 = mkRand(); let rand2 = mkRand(); return rand1 + rand2; } }; invibes.getCookie = function (name) { if (!storage.cookiesAreEnabled()) { return; } if (!invibes.optIn || !invibes.purposes[0]) { return; } return storage.getCookie(name); }; let initDomainId = function (options) { let cookiePersistence = { cname: 'ivbsdid', load: function () { let str = invibes.getCookie(this.cname) || ''; try { return JSON.parse(str); } catch (e) { } } }; options = options || {}; var persistence = options.persistence || cookiePersistence; let state = persistence.load(); return state ? (state.id || state.tempId) : undefined; }; let keywords = (function () { const cap = 300; let headTag = document.getElementsByTagName('head')[0]; let metaTag = headTag ? headTag.getElementsByTagName('meta') : []; function parse(str, cap) { let parsedStr = str.replace(/[<>~|\\"`!@#$%^&*()=+?]/g, ''); function onlyUnique(value, index, self) { return value !== '' && self.indexOf(value) === index; } let words = parsedStr.split(/[\s,;.:]+/); let uniqueWords = words.filter(onlyUnique); parsedStr = ''; for (let i = 0; i < uniqueWords.length; i++) { parsedStr += uniqueWords[i]; if (parsedStr.length >= cap) { return parsedStr; } if (i < uniqueWords.length - 1) { parsedStr += ','; } } return parsedStr; } function gt(cap, prefix) { cap = cap || 300; prefix = prefix || ''; let title = document.title || headTag ? headTag.getElementsByTagName('title')[0] ? headTag.getElementsByTagName('title')[0].innerHTML : '' : ''; return parse(prefix + ',' + title, cap); } function gmeta(metaName, cap, prefix) { metaName = metaName || 'keywords'; cap = cap || 100; prefix = prefix || ''; let fallbackKw = prefix; for (let i = 0; i < metaTag.length; i++) { if (metaTag[i].name && metaTag[i].name.toLowerCase() === metaName.toLowerCase()) { let kw = prefix + ',' + metaTag[i].content || ''; return parse(kw, cap); } else if (metaTag[i].name && metaTag[i].name.toLowerCase().indexOf(metaName.toLowerCase()) > -1) { fallbackKw = prefix + ',' + metaTag[i].content || ''; } } return parse(fallbackKw, cap); } let kw = gmeta('keywords', cap); if (!kw || kw.length < cap - 8) { kw = gmeta('description', cap, kw); if (!kw || kw.length < cap - 8) { kw = gt(cap, kw); } } return kw; }()); // ===================== export function resetInvibes() { invibes.optIn = undefined; invibes.noCookies = undefined; invibes.dom = undefined; invibes.bidResponse = undefined; invibes.domainOptions = undefined; } export function stubDomainOptions(persistence) { invibes.domainOptions = { persistence: persistence }; }