diff --git a/modules/dynamicAdBoostRtdProvider.js b/modules/dynamicAdBoostRtdProvider.js index a68567b1ca3..b89a33ecb1d 100644 --- a/modules/dynamicAdBoostRtdProvider.js +++ b/modules/dynamicAdBoostRtdProvider.js @@ -7,7 +7,7 @@ import { submodule } from '../src/hook.js' import { loadExternalScript } from '../src/adloader.js'; import { getGlobal } from '../src/prebidGlobal.js'; -import { deepAccess, deepSetValue, isEmptyStr } from '../src/utils.js'; +import { deepAccess, deepSetValue } from '../src/utils.js'; import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; /** @@ -29,7 +29,7 @@ let dabStartTime; // Array of div IDs to track let dynamicAdBoostAdUnits = {}; -function init(config, userConsent) { +function init() { dabStartDate = new Date(); dabStartTime = dabStartDate.getTime(); if (!CLIENT_SUPPORTS_IO) { @@ -37,47 +37,30 @@ function init(config, userConsent) { } // Create an Intersection Observer instance observer = new IntersectionObserver(dabHandleIntersection, dabOptions); - if (config.params.keyId) { - let keyId = config.params.keyId; - if (keyId && !isEmptyStr(keyId)) { - let dabDivIdsToTrack = config.params.adUnits; - let dabInterval = setInterval(function() { - // Observe each div by its ID - dabDivIdsToTrack.forEach(divId => { - let div = document.getElementById(divId); - if (div) { - observer.observe(div); - } - }); + let keyId = 'rtd-' + window.location.hostname; - let dabDateNow = new Date(); - let dabTimeNow = dabDateNow.getTime(); - let dabElapsedSeconds = Math.floor((dabTimeNow - dabStartTime) / 1000); - let elapsedThreshold = 30; - if (config.params.threshold) { - elapsedThreshold = config.params.threshold; - } - if (dabElapsedSeconds >= elapsedThreshold) { - clearInterval(dabInterval); // Stop - loadLmScript(keyId); - } - }, 1000); + let dabInterval = setInterval(function() { + let dabDateNow = new Date(); + let dabTimeNow = dabDateNow.getTime(); + let dabElapsedSeconds = Math.floor((dabTimeNow - dabStartTime) / 1000); + let elapsedThreshold = 0; - return true; + if (dabElapsedSeconds >= elapsedThreshold) { + clearInterval(dabInterval); // Stop + loadLmScript(keyId); } - } - return false; + }, 1000); + + return true; } function loadLmScript(keyId) { - let viewableAdUnits = Object.keys(dynamicAdBoostAdUnits); - let viewableAdUnitsCSV = viewableAdUnits.join(','); - const scriptUrl = `${SCRIPT_URL}/${keyId}.js?viewableAdUnits=${viewableAdUnitsCSV}`; + const scriptUrl = `${SCRIPT_URL}/${keyId}.js`; loadExternalScript(scriptUrl, MODULE_TYPE_RTD, MODULE_NAME); observer.disconnect(); } -function getBidRequestData(reqBidsConfigObj, callback, config, userConsent) { +function getBidRequestData(reqBidsConfigObj, callback) { const reqAdUnits = reqBidsConfigObj.adUnits || getGlobal().adUnits; if (Array.isArray(reqAdUnits)) { diff --git a/modules/dynamicAdBoostRtdProvider.md b/modules/dynamicAdBoostRtdProvider.md index 93efe3b3f97..c249208063b 100644 --- a/modules/dynamicAdBoostRtdProvider.md +++ b/modules/dynamicAdBoostRtdProvider.md @@ -26,12 +26,7 @@ pbjs.setConfig( auctionDelay: 2000, dataProviders: [ { - name: "dynamicAdBoost", - params: { - keyId: "[PROVIDED_KEY]", // Your provided Dynamic AdBoost keyId - adUnits: ["allowedAdUnit1", "allowedAdUnit2"], - threshold: 35 // optional - } + name: "dynamicAdBoost" } ] } diff --git a/modules/luponmediaBidAdapter.js b/modules/luponmediaBidAdapter.js index 3dab4524db1..9d06d0b90c1 100755 --- a/modules/luponmediaBidAdapter.js +++ b/modules/luponmediaBidAdapter.js @@ -1,572 +1,167 @@ -import { - _each, - deepAccess, - deepSetValue, - generateUUID, - isArray, - isEmpty, - isFn, - isPlainObject, - logError, - logMessage, - logWarn, - parseSizesInput, - sizeTupleToRtbSize, - sizesToSizeTuples -} from '../src/utils.js'; +import {logError, logMessage, logWarn, deepSetValue} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {config} from '../src/config.js'; import {BANNER} from '../src/mediaTypes.js'; +import {ortbConverter} from '../libraries/ortbConverter/converter.js'; +import {config} from '../src/config.js'; const BIDDER_CODE = 'luponmedia'; -const ENDPOINT_URL = 'https://rtb.adxpremium.services/openrtb2/auction'; - -const DIGITRUST_PROP_NAMES = { - PREBID_SERVER: { - id: 'id', - keyv: 'keyv' - } -}; - -var sizeMap = { - 1: '468x60', - 2: '728x90', - 5: '120x90', - 7: '125x125', - 8: '120x600', - 9: '160x600', - 10: '300x600', - 13: '200x200', - 14: '250x250', - 15: '300x250', - 16: '336x280', - 17: '240x400', - 19: '300x100', - 31: '980x120', - 32: '250x360', - 33: '180x500', - 35: '980x150', - 37: '468x400', - 38: '930x180', - 39: '750x100', - 40: '750x200', - 41: '750x300', - 42: '2x4', - 43: '320x50', - 44: '300x50', - 48: '300x300', - 53: '1024x768', - 54: '300x1050', - 55: '970x90', - 57: '970x250', - 58: '1000x90', - 59: '320x80', - 60: '320x150', - 61: '1000x1000', - 64: '580x500', - 65: '640x480', - 66: '930x600', - 67: '320x480', - 68: '1800x1000', - 72: '320x320', - 73: '320x160', - 78: '980x240', - 79: '980x300', - 80: '980x400', - 83: '480x300', - 85: '300x120', - 90: '548x150', - 94: '970x310', - 95: '970x100', - 96: '970x210', - 101: '480x320', - 102: '768x1024', - 103: '480x280', - 105: '250x800', - 108: '320x240', - 113: '1000x300', - 117: '320x100', - 125: '800x250', - 126: '200x600', - 144: '980x600', - 145: '980x150', - 152: '1000x250', - 156: '640x320', - 159: '320x250', - 179: '250x600', - 195: '600x300', - 198: '640x360', - 199: '640x200', - 213: '1030x590', - 214: '980x360', - 221: '1x1', - 229: '320x180', - 230: '2000x1400', - 232: '580x400', - 234: '6x6', - 251: '2x2', - 256: '480x820', - 257: '400x600', - 258: '500x200', - 259: '998x200', - 264: '970x1000', - 265: '1920x1080', - 274: '1800x200', - 278: '320x500', - 282: '320x400', - 288: '640x380', - 548: '500x1000', - 550: '980x480', - 552: '300x200', - 558: '640x640' -}; - -_each(sizeMap, (item, key) => sizeMap[item] = key); - -export const spec = { - code: BIDDER_CODE, - supportedMediaTypes: [BANNER], - isBidRequestValid: function (bid) { - return !!(bid.params && bid.params.siteId && bid.params.keyId); // TODO: check for siteId and keyId - }, - buildRequests: function (bidRequests, bidderRequest) { - const bRequest = { - method: 'POST', - url: ENDPOINT_URL, - data: null, - options: {}, - bidderRequest - }; - - let currentImps = []; - - for (let i = 0, len = bidRequests.length; i < len; i++) { - let newReq = newOrtbBidRequest(bidRequests[i], bidderRequest, currentImps); - currentImps = newReq.imp; - bRequest.data = JSON.stringify(newReq); - } +const keyIdRegex = /^uid(?:@[\w-]+)?_.*$/; - return bRequest; - }, - interpretResponse: (response, request) => { - const bidResponses = []; - var respCur = 'USD'; - let parsedRequest = JSON.parse(request.data); - let parsedReferrer = parsedRequest.site && parsedRequest.site.ref ? parsedRequest.site.ref : ''; - try { - if (response.body && response.body.seatbid && isArray(response.body.seatbid)) { - // Supporting multiple bid responses for same adSize - respCur = response.body.cur || respCur; - response.body.seatbid.forEach(seatbidder => { - seatbidder.bid && - isArray(seatbidder.bid) && - seatbidder.bid.forEach(bid => { - let newBid = { - requestId: bid.impid, - cpm: (parseFloat(bid.price) || 0).toFixed(2), - width: bid.w, - height: bid.h, - creativeId: bid.crid || bid.id, - dealId: bid.dealid, - currency: respCur, - netRevenue: false, - ttl: 300, - referrer: parsedReferrer, - ad: bid.adm, - adomain: bid.adomain || [], - meta: { - advertiserDomains: bid && bid.adomain ? bid.adomain : [] - } - }; - - bidResponses.push(newBid); - }); - }); - } - } catch (error) { - logError(error); - } - return bidResponses; - }, - getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent) { - let allUserSyncs = []; - if (!hasSynced && (syncOptions.iframeEnabled || syncOptions.pixelEnabled)) { - responses.forEach(csResp => { - if (csResp.body && csResp.body.ext && csResp.body.ext.usersyncs) { - try { - let response = csResp.body.ext.usersyncs; - let bidders = response.bidder_status; - for (let synci in bidders) { - let thisSync = bidders[synci]; - if (thisSync.no_cookie) { - let url = thisSync.usersync.url; - let type = thisSync.usersync.type; - - if (!url) { - logError(`No sync url for bidder luponmedia.`); - } else if ((type === 'image' || type === 'redirect') && syncOptions.pixelEnabled) { - logMessage(`Invoking image pixel user sync for luponmedia`); - allUserSyncs.push({type: 'image', url: url}); - } else if (type == 'iframe' && syncOptions.iframeEnabled) { - logMessage(`Invoking iframe user sync for luponmedia`); - allUserSyncs.push({type: 'iframe', url: url}); - } else { - logError(`User sync type "${type}" not supported for luponmedia`); - } - } - } - } catch (e) { - logError(e); - } - } - }); - } else { - logWarn('Luponmedia: Please enable iframe/pixel based user sync.'); - } +const buildServerUrl = (keyId) => { + const match = String(keyId).match(/@([^_]+)_/); + let host = 'rtb'; - hasSynced = true; - return allUserSyncs; + if (match) { + host = match[1]; } -}; -export function hasValidSupplyChainParams(schain) { - let isValid = false; - const requiredFields = ['asi', 'sid', 'hp']; - if (!schain.nodes) return isValid; - isValid = schain.nodes.reduce((status, node) => { - if (!status) return status; - return requiredFields.every(field => node[field]); - }, true); - if (!isValid) logError('LuponMedia: required schain params missing'); - return isValid; + return `https://${host}.adxpremium.services/openrtb2/auction` } -var hasSynced = false; +function hasRtd() { + const rtdConfigs = config.getConfig('realTimeData.dataProviders') || []; -export function resetUserSync() { - hasSynced = false; + return Boolean(rtdConfigs.find(provider => provider.name === 'dynamicAdBoost')); } -export function masSizeOrdering(sizes) { - const MAS_SIZE_PRIORITY = [15, 2, 9]; - - return sizes.sort((first, second) => { - // sort by MAS_SIZE_PRIORITY priority order - const firstPriority = MAS_SIZE_PRIORITY.indexOf(first); - const secondPriority = MAS_SIZE_PRIORITY.indexOf(second); - - if (firstPriority > -1 || secondPriority > -1) { - if (firstPriority === -1) { - return 1; - } - if (secondPriority === -1) { - return -1; - } - return firstPriority - secondPriority; - } - - // and finally ascending order - return first - second; - }); -} - -function newOrtbBidRequest(bidRequest, bidderRequest, currentImps) { - bidRequest.startTime = new Date().getTime(); - - const bannerParams = deepAccess(bidRequest, 'mediaTypes.banner'); - - let bannerSizes = []; - - if (bannerParams && bannerParams.sizes) { - // get banner sizes in form [{ w: , h: }, ...] - const format = sizesToSizeTuples(bannerParams.sizes).map(sizeTupleToRtbSize); - bannerSizes = format; - } - - const data = { - id: bidderRequest.bidderRequestId, - test: config.getConfig('debug') ? 1 : 0, - source: { - tid: bidderRequest.ortb2?.source?.tid, - }, - tmax: bidderRequest.timeout, - imp: currentImps.concat([{ - id: bidRequest.bidId, - secure: 1, - ext: { - [bidRequest.bidder]: bidRequest.params - }, - banner: { - format: bannerSizes - } - }]), - ext: { - prebid: { - targeting: { - includewinners: true, - // includebidderkeys always false for openrtb - includebidderkeys: false - } - } - }, - user: { - } - }; - - let bidFloor; - if (isFn(bidRequest.getFloor) && !config.getConfig('disableFloors')) { - let floorInfo; - try { - floorInfo = bidRequest.getFloor({ - currency: 'USD', - mediaType: 'video', - size: parseSizes(bidRequest, 'video') - }); - } catch (e) { - logError('LuponMedia: getFloor threw an error: ', e); - } - bidFloor = isPlainObject(floorInfo) && floorInfo.currency === 'USD' && !isNaN(parseInt(floorInfo.floor)) ? parseFloat(floorInfo.floor) : undefined; - } else { - bidFloor = parseFloat(deepAccess(bidRequest, 'params.floor')); - } - if (!isNaN(bidFloor)) { - data.imp[0].bidfloor = bidFloor; - } +export const converter = ortbConverter({ + context: { + netRevenue: false, + ttl: 300, + mediaType: BANNER, + }, + imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); - appendSiteAppDevice(data, bidRequest, bidderRequest); + imp.ext = imp.ext || {}; - const digiTrust = _getDigiTrustQueryParams(bidRequest, 'PREBID_SERVER'); - if (digiTrust) { - deepSetValue(data, 'user.ext.digitrust', digiTrust); - } + const hasRtdEnabled = hasRtd(); - if (bidderRequest.gdprConsent) { - // note - gdprApplies & consentString may be undefined in certain use-cases for consentManagement module - let gdprApplies; - if (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') { - gdprApplies = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; + if (!hasRtdEnabled) { + logWarn('LuponMedia: Enable the DynamicAdBoost RTD Module to optimize revenue and performance.') } - deepSetValue(data, 'regs.ext.gdpr', gdprApplies); - deepSetValue(data, 'user.ext.consent', bidderRequest.gdprConsent.consentString); - } - - if (bidderRequest.uspConsent) { - deepSetValue(data, 'regs.ext.us_privacy', bidderRequest.uspConsent); - } - - // Set user uuid - deepSetValue(data, 'user.id', generateUUID()); + imp.ext.luponmedia = imp.ext.luponmedia || {}; + imp.ext.luponmedia.placement_id = bidRequest.adUnitCode; + imp.ext.luponmedia.keyId = bidRequest.params.keyId; + imp.ext.luponmedia.siteId = bidRequest.params.siteId; + imp.ext.luponmedia.rtd = hasRtdEnabled; - // set crumbs - if (bidRequest.crumbs && bidRequest.crumbs.pubcid) { - deepSetValue(data, 'user.buyeruid', bidRequest.crumbs.pubcid); - } else { - deepSetValue(data, 'user.buyeruid', generateUUID()); - } + return imp; + }, + bidResponse(buildBidResponse, bid, context) { + const bidResponse = buildBidResponse(bid, context); - if (bidRequest.userId && typeof bidRequest.userId === 'object' && - (bidRequest.userId.tdid || bidRequest.userId.pubcid || bidRequest.userId.lipb || bidRequest.userId.idl_env)) { - deepSetValue(data, 'user.ext.eids', []); - - if (bidRequest.userId.tdid) { - data.user.ext.eids.push({ - source: 'adserver.org', - uids: [{ - id: bidRequest.userId.tdid, - ext: { - rtiPartner: 'TDID' - } - }] - }); + if (!bidResponse.creativeId) { + bidResponse.creativeId = bid.crid || bid.id; } - if (bidRequest.userId.pubcid) { - data.user.ext.eids.push({ - source: 'pubcommon', - uids: [{ - id: bidRequest.userId.pubcid, - }] - }); + if (!bidResponse.dealId && bid.dealid) { + bidResponse.dealId = bid.dealid; } - // support liveintent ID - if (bidRequest.userId.lipb && bidRequest.userId.lipb.lipbid) { - data.user.ext.eids.push({ - source: 'liveintent.com', - uids: [{ - id: bidRequest.userId.lipb.lipbid - }] - }); - - data.user.ext.tpid = { - source: 'liveintent.com', - uid: bidRequest.userId.lipb.lipbid - }; - - if (Array.isArray(bidRequest.userId.lipb.segments) && bidRequest.userId.lipb.segments.length) { - deepSetValue(data, 'rp.target.LIseg', bidRequest.userId.lipb.segments); - } + if (context.bidRequest?.ortb2?.site?.ref) { + bidResponse.referrer = context.bidRequest.ortb2.site.ref; } - // support identityLink (aka LiveRamp) - if (bidRequest.userId.idl_env) { - data.user.ext.eids.push({ - source: 'liveramp.com', - uids: [{ - id: bidRequest.userId.idl_env - }] - }); - } - } - - if (config.getConfig('coppa') === true) { - deepSetValue(data, 'regs.coppa', 1); - } + return bidResponse; + }, +}); +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + isBidRequestValid: function (bid) { + return keyIdRegex.test(bid?.params?.keyId); + }, + buildRequests: function (bidRequests, bidderRequest) { + const data = converter.toORTB({ bidderRequest, bidRequests }); - if (bidRequest.schain && hasValidSupplyChainParams(bidRequest.schain)) { - deepSetValue(data, 'source.ext.schain', bidRequest.schain); - } + if (bidderRequest.gdprConsent) { + let gdprApplies; - // TODO: getConfig('fpd.context') should not have worked even with legacy FPD support - 'fpd' gets translated - // into 'ortb2' by `setConfig` - // Unclear what the intent was here - maybe `const {context: siteData, user: userData} = getLegacyFpd(config.getConfig('ortb2'))` ? - // (with PB7 `config.getConfig('ortb2')` should be replaced by `bidderRequest.ortb2`) - const siteData = Object.assign({}, bidRequest.params.inventory, config.getConfig('fpd.context')); - const userData = Object.assign({}, bidRequest.params.visitor, config.getConfig('fpd.user')); - - if (!isEmpty(siteData) || !isEmpty(userData)) { - const bidderData = { - bidders: [ bidderRequest.bidderCode ], - config: { - fpd: {} + if (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') { + gdprApplies = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; } - }; - if (!isEmpty(siteData)) { - bidderData.config.fpd.site = siteData; + deepSetValue(data, 'regs.ext.gdpr', gdprApplies); + deepSetValue(data, 'user.ext.consent', bidderRequest.gdprConsent.consentString); } - if (!isEmpty(userData)) { - bidderData.config.fpd.user = userData; + if (bidderRequest.uspConsent) { + deepSetValue(data, 'regs.ext.us_privacy', bidderRequest.uspConsent); } - deepSetValue(data, 'ext.prebid.bidderconfig.0', bidderData); - } - - const pbAdSlot = deepAccess(bidRequest, 'ortb2Imp.ext.data.pbadslot'); - if (typeof pbAdSlot === 'string' && pbAdSlot) { - deepSetValue(data.imp[0].ext, 'context.data.adslot', pbAdSlot); - } + const serverUrl = buildServerUrl(bidRequests[0].params.keyId); - return data; -} - -function _getDigiTrustQueryParams(bidRequest = {}, endpointName) { - if (!endpointName || !DIGITRUST_PROP_NAMES[endpointName]) { - return null; - } - const propNames = DIGITRUST_PROP_NAMES[endpointName]; + return { + method: 'POST', + url: serverUrl, + data, + }; + }, + interpretResponse: (response, request) => { + return converter.fromORTB({response: response.body, request: request.data}).bids; + }, + getUserSyncs: function (syncOptions, responses) { + let allUserSyncs = []; - function getDigiTrustId() { - const bidRequestDigitrust = deepAccess(bidRequest, 'userId.digitrustid.data'); - if (bidRequestDigitrust) { - return bidRequestDigitrust; + if (hasSynced) { + return allUserSyncs; } - let digiTrustUser = (window.DigiTrust && (config.getConfig('digiTrustId') || window.DigiTrust.getUser({member: 'T9QSFKPDN9'}))); - return (digiTrustUser && digiTrustUser.success && digiTrustUser.identity) || null; - } - - let digiTrustId = getDigiTrustId(); - // Verify there is an ID and this user has not opted out - if (!digiTrustId || (digiTrustId.privacy && digiTrustId.privacy.optout)) { - return null; - } + if (!syncOptions.iframeEnabled && !syncOptions.pixelEnabled) { + logWarn('Luponmedia: Please enable iframe/pixel based user sync.'); + hasSynced = true; + return allUserSyncs; + } - const digiTrustQueryParams = { - [propNames.id]: digiTrustId.id, - [propNames.keyv]: digiTrustId.keyv - }; - if (propNames.pref) { - digiTrustQueryParams[propNames.pref] = 0; - } - return digiTrustQueryParams; -} + responses.forEach(csResp => { + if (!csResp?.body?.ext?.usersyncs) { + return; + } -function _getPageUrl(bidRequest, bidderRequest) { - // TODO: do the fallbacks make sense here? - let pageUrl = bidderRequest.refererInfo.page; - if (bidRequest.params.referrer) { - pageUrl = bidRequest.params.referrer; - } else if (!pageUrl) { - pageUrl = bidderRequest.refererInfo.topmostLocation; - } - return bidRequest.params.secure ? pageUrl.replace(/^http:/i, 'https:') : pageUrl; -} + try { + const response = csResp.body.ext.usersyncs; + const bidders = response.bidder_status; -function appendSiteAppDevice(data, bidRequest, bidderRequest) { - if (!data) return; + for (let synci in bidders) { + const thisSync = bidders[synci]; - // ORTB specifies app OR site - if (typeof config.getConfig('app') === 'object') { - data.app = config.getConfig('app'); - } else { - data.site = { - page: _getPageUrl(bidRequest, bidderRequest) - } - } - if (typeof config.getConfig('device') === 'object') { - data.device = config.getConfig('device'); - } -} + if (!thisSync.no_cookie) { + continue; + } -/** - * @param sizes - * @returns {*} - */ -function mapSizes(sizes) { - return parseSizesInput(sizes) - // map sizes while excluding non-matches - .reduce((result, size) => { - let mappedSize = parseInt(sizeMap[size], 10); - if (mappedSize) { - result.push(mappedSize); + const url = thisSync.usersync.url; + const type = thisSync.usersync.type; + + if (!url) { + logError(`No sync url for bidder luponmedia.`); + } else if ((type === 'image' || type === 'redirect') && syncOptions.pixelEnabled) { + logMessage(`Invoking image pixel user sync for luponmedia`); + allUserSyncs.push({ type: 'image', url: url }); + } else if (type === 'iframe' && syncOptions.iframeEnabled) { + logMessage(`Invoking iframe user sync for luponmedia`); + allUserSyncs.push({ type: 'iframe', url: url }); + } else { + logError(`User sync type "${type}" not supported for luponmedia`); + } + } + } catch (e) { + logError(e); } - return result; - }, []); -} + }); -function parseSizes(bid, mediaType) { - let params = bid.params; - if (mediaType === 'video') { - let size = []; - if (params.video && params.video.playerWidth && params.video.playerHeight) { - size = [ - params.video.playerWidth, - params.video.playerHeight - ]; - } else if (Array.isArray(deepAccess(bid, 'mediaTypes.video.playerSize')) && bid.mediaTypes.video.playerSize.length === 1) { - size = bid.mediaTypes.video.playerSize[0]; - } else if (Array.isArray(bid.sizes) && bid.sizes.length > 0 && Array.isArray(bid.sizes[0]) && bid.sizes[0].length > 1) { - size = bid.sizes[0]; - } - return size; - } + hasSynced = true; - // Deprecated: temp legacy support - let sizes = []; - if (Array.isArray(params.sizes)) { - sizes = params.sizes; - } else if (typeof deepAccess(bid, 'mediaTypes.banner.sizes') !== 'undefined') { - sizes = mapSizes(bid.mediaTypes.banner.sizes); - } else if (Array.isArray(bid.sizes) && bid.sizes.length > 0) { - sizes = mapSizes(bid.sizes); - } else { - logWarn('LuponMedia: no sizes are setup or found'); - } + return allUserSyncs; + }, +}; - return masSizeOrdering(sizes); +let hasSynced = false; + +// we need this for tests +export function resetUserSync() { + hasSynced = false; } registerBidder(spec); diff --git a/modules/luponmediaBidAdapter.md b/modules/luponmediaBidAdapter.md index cec0aa3ec84..92e26464a83 100644 --- a/modules/luponmediaBidAdapter.md +++ b/modules/luponmediaBidAdapter.md @@ -17,15 +17,14 @@ Module that connects to LuponMedia's demand sources code: 'test-div', mediaTypes: { banner: { - sizes: [[300, 250]], // a display size + sizes: [[300, 250]] } }, bids: [ { bidder: "luponmedia", params: { - siteId: 12345, - keyId: '4o2c4' + keyId: 'uid@test_12345' } } ] diff --git a/test/spec/modules/dynamicAdBoostRtdProvider_spec.js b/test/spec/modules/dynamicAdBoostRtdProvider_spec.js index 66c24435589..35d0affeed1 100644 --- a/test/spec/modules/dynamicAdBoostRtdProvider_spec.js +++ b/test/spec/modules/dynamicAdBoostRtdProvider_spec.js @@ -1,48 +1,6 @@ import { subModuleObj as rtdProvider } from 'modules/dynamicAdBoostRtdProvider.js'; -import { loadExternalScript } from '../../../src/adloader.js'; import { expect } from 'chai'; -const configWithParams = { - params: { - keyId: 'dynamic', - adUnits: ['gpt-123'], - threshold: 1 - } -}; - -const configWithoutRequiredParams = { - params: { - keyId: '' - } -}; - -describe('dynamicAdBoost', function() { - let clock; - let sandbox; - beforeEach(function () { - sandbox = sinon.sandbox.create(); - clock = sandbox.useFakeTimers(Date.now()); - }); - afterEach(function () { - sandbox.restore(); - }); - describe('init', function() { - describe('initialize without expected params', function() { - it('fails initalize when keyId is not present', function() { - expect(rtdProvider.init(configWithoutRequiredParams)).to.be.false; - }) - }) - - describe('initialize with expected params', function() { - it('successfully initialize with load script', function() { - expect(rtdProvider.init(configWithParams)).to.be.true; - clock.tick(1000); - expect(loadExternalScript.called).to.be.true; - }) - }); - }); -}) - describe('markViewed tests', function() { let sandbox; const mockObserver = { diff --git a/test/spec/modules/luponmediaBidAdapter_spec.js b/test/spec/modules/luponmediaBidAdapter_spec.js index 3d9be5a40bf..564d2ae3ba2 100755 --- a/test/spec/modules/luponmediaBidAdapter_spec.js +++ b/test/spec/modules/luponmediaBidAdapter_spec.js @@ -1,297 +1,218 @@ -import { resetUserSync, spec, hasValidSupplyChainParams } from 'modules/luponmediaBidAdapter.js'; -const ENDPOINT_URL = 'https://rtb.adxpremium.services/openrtb2/auction'; +// tests/luponmediaBidAdapter_spec.js +import { resetUserSync, spec, converter, storage } from 'modules/luponmediaBidAdapter.js'; +import sinon from 'sinon'; +import { expect } from 'chai'; describe('luponmediaBidAdapter', function () { + let sandbox; + + beforeEach(function () { + sandbox = sinon.createSandbox(); + }); + + afterEach(function () { + sandbox.restore(); + }); + describe('isBidRequestValid', function () { - let bid = { - 'bidder': 'luponmedia', - 'params': { - 'siteId': 12345, - 'keyId': '4o2c4' - }, - 'adUnitCode': 'test-div', - 'sizes': [[300, 250]], - 'bidId': 'g1987234bjkads', - 'bidderRequestId': '290348ksdhkas89324', - 'auctionId': '20384rlek235', + const bid = { + bidder: 'luponmedia', + params: { keyId: 'uid@eu_test_300_600' }, + adUnitCode: 'test-div', + sizes: [[300, 250]], + bidId: 'g1987234bjkads' }; - it('should return true when required params are found', function () { - expect(spec.isBidRequestValid(bid)).to.equal(true); + it('should return true when required param is found and it is valid', function () { + expect(spec.isBidRequestValid(bid)).to.be.true; }); - it('should return false when required params are not passed', function () { - let invalidBid = Object.assign({}, bid); - delete invalidBid.params; - invalidBid.params = { - 'siteId': 12345 - }; - expect(spec.isBidRequestValid(invalidBid)).to.equal(false); + it('should return true with required and without optional param', function () { + bid.params = { keyId: 'uid_test_300_600' }; + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + + it('should return false when keyId is not in the required format', function () { + bid.params = { keyId: 12345 }; + expect(spec.isBidRequestValid(bid)).to.be.false; }); }); describe('buildRequests', function () { - let bidRequests = [ + const bidRequests = [ { - 'bidder': 'luponmedia', - 'params': { - 'siteId': 303522, - 'keyId': '4o2c4' - }, - 'crumbs': { - 'pubcid': '8d8b16cb-1383-4a0f-b4bb-0be28464d974' - }, - 'mediaTypes': { - 'banner': { - 'sizes': [ - [ - 300, - 250 - ] - ] - } - }, - 'adUnitCode': 'div-gpt-ad-1533155193780-2', - 'transactionId': '585d96a5-bd93-4a89-b8ea-0f546f3aaa82', - 'sizes': [ - [ - 300, - 250 - ] - ], - 'bidId': '268a30af10dd6f', - 'bidderRequestId': '140411b5010a2a', - 'auctionId': '7376c117-b7aa-49f5-a661-488543deeefd', - 'src': 'client', - 'bidRequestsCount': 1, - 'bidderRequestsCount': 1, - 'bidderWinsCount': 0, - 'schain': { - 'ver': '1.0', - 'complete': 1, - 'nodes': [ - { - 'asi': 'novi.ba', - 'sid': '199424', - 'hp': 1 - } - ] - } + bidder: 'luponmedia', + params: { keyId: 'uid_test_300_600', placement_id: 'test-div' }, + mediaTypes: { banner: { sizes: [[300, 600]] } }, + adUnitCode: 'test-div', + transactionId: 'txn-id', + bidId: 'bid-id', + ortb2: { device: { ua: 'test-agent' } } } ]; - let bidderRequest = { - 'bidderCode': 'luponmedia', - 'auctionId': '7376c117-b7aa-49f5-a661-488543deeefd', - 'bidderRequestId': '140411b5010a2a', - 'bids': [ - { - 'bidder': 'luponmedia', - 'params': { - 'siteId': 303522, - 'keyId': '4o2c4' - }, - 'crumbs': { - 'pubcid': '8d8b16cb-1383-4a0f-b4bb-0be28464d974' - }, - 'mediaTypes': { - 'banner': { - 'sizes': [ - [ - 300, - 250 - ] - ] - } - }, - 'adUnitCode': 'div-gpt-ad-1533155193780-2', - 'transactionId': '585d96a5-bd93-4a89-b8ea-0f546f3aaa82', - 'sizes': [ - [ - 300, - 250 - ] - ], - 'bidId': '268a30af10dd6f', - 'bidderRequestId': '140411b5010a2a', - 'auctionId': '7376c117-b7aa-49f5-a661-488543deeefd', - 'src': 'client', - 'bidRequestsCount': 1, - 'bidderRequestsCount': 1, - 'bidderWinsCount': 0, - 'schain': { - 'ver': '1.0', - 'complete': 1, - 'nodes': [ - { - 'asi': 'novi.ba', - 'sid': '199424', - 'hp': 1 - } - ] - } - } - ], - 'auctionStart': 1587413920820, - 'timeout': 1500, - 'refererInfo': { - 'page': 'https://novi.ba/clanak/176067/fast-car-beginner-s-guide-to-tuning-turbo-engines', - 'reachedTop': true, - 'numIframes': 0, - 'stack': [ - 'https://novi.ba/clanak/176067/fast-car-beginner-s-guide-to-tuning-turbo-engines' - ] - }, - 'start': 1587413920835, - ortb2: { - source: { - tid: 'mock-tid' - } + const bidderRequest = { + bidderCode: 'luponmedia', + gdprConsent: { + gdprApplies: true }, + uspConsent: true }; - it('sends bid request to ENDPOINT via POST', function () { - const requests = spec.buildRequests(bidRequests, bidderRequest); - let dynRes = JSON.parse(requests.data); - expect(requests.url).to.equal(ENDPOINT_URL); - expect(requests.method).to.equal('POST'); - expect(JSON.parse(requests.data)).to.deep.include({ - 'test': 0, - 'source': { - tid: 'mock-tid', - 'ext': {'schain': {'ver': '1.0', 'complete': 1, 'nodes': [{'asi': 'novi.ba', 'sid': '199424', 'hp': 1}]}} - }, - 'tmax': 1500, - 'imp': [{ - 'id': '268a30af10dd6f', - 'secure': 1, - 'ext': {'luponmedia': {'siteId': 303522, 'keyId': '4o2c4'}}, - 'banner': {'format': [{'w': 300, 'h': 250}]} - }], - 'ext': {'prebid': {'targeting': {'includewinners': true, 'includebidderkeys': false}}}, - 'user': {'id': dynRes.user.id, 'buyeruid': '8d8b16cb-1383-4a0f-b4bb-0be28464d974'}, - 'site': {'page': 'https://novi.ba/clanak/176067/fast-car-beginner-s-guide-to-tuning-turbo-engines'} - }); + it('sends bid request to default endpoint', function () { + const req = spec.buildRequests(bidRequests, bidderRequest); + + expect(req.url).to.include('https://rtb.adxpremium.services/openrtb2/auction'); + expect(req.method).to.equal('POST'); + expect(req.data.imp[0].ext.luponmedia.placement_id).to.equal('test-div'); + expect(req.data.imp[0].ext.luponmedia.keyId).to.equal('uid_test_300_600'); + }); + + it('sends bid request to endpoint specified in keyId', function () { + bidRequests[0].params.keyId = 'uid@eu_test_300_600'; + + const req = spec.buildRequests(bidRequests, bidderRequest); + expect(req.url).to.include('https://eu.adxpremium.services/openrtb2/auction'); }); }); describe('interpretResponse', function () { it('should get correct banner bid response', function () { - let response = { - 'id': '4776d680-15a2-45c3-bad5-db6bebd94a06', - 'seatbid': [ + const response = { + id: 'resp-id', + seatbid: [ { - 'bid': [ + bid: [ { - 'id': '2a122246ef72ea', - 'impid': '2a122246ef72ea', - 'price': 0.43, - 'adm': ' ', - 'adid': '56380110', - 'adomain': [ - 'mi.betrivers.com' - ], - 'cid': '44724710', - 'crid': '443801010', - 'w': 300, - 'h': 250, - 'ext': { - 'prebid': { - 'targeting': { - 'hb_bidder': 'luponmedia', - 'hb_pb': '0.40', - 'hb_size': '300x250' + id: 'bid123', + impid: 'bid123', + price: 0.43, + adm: '
Ad Markup
', + crid: 'creative-id', + w: 300, + h: 250, + ext: { + prebid: { + targeting: { + hb_bidder: 'luponmedia', + hb_pb: '0.40', + hb_size: '300x250' }, - 'type': 'banner' + type: 'banner' } } } ], - 'seat': 'luponmedia' + seat: 'luponmedia' } ], - 'cur': 'USD', - 'ext': { - 'responsetimemillis': { - 'luponmedia': 233 - }, - 'tmaxrequest': 1500, - 'usersyncs': { - 'status': 'ok', - 'bidder_status': [] - } + cur: 'USD' + }; + + const bidRequests = [ + { + bidId: 'bid123', + adUnitCode: 'test-div', + params: { keyId: 'uid_test_300_600' }, + mediaTypes: { banner: { sizes: [[300, 250]] } } } + ]; + + const bidderRequest = { refererInfo: { referer: 'https://example.com' } }; + const ortbRequest = converter.toORTB({ bidRequests, bidderRequest }); + + const result = spec.interpretResponse({ status: 200, body: response }, { data: ortbRequest }); + + expect(result).to.be.an('array').with.lengthOf(1); + expect(result[0]).to.include({ + requestId: 'bid123', + cpm: 0.43, + width: 300, + height: 250, + creativeId: 'creative-id', + currency: 'USD', + ttl: 300, + ad: '
Ad Markup
' + }); + }); + + it('should enrich bidResponse with crid, dealId, and referrer if missing', function () { + const response = { + id: 'resp-id', + seatbid: [ + { + bid: [ + { + id: 'bid456', + impid: 'bid456', + price: 0.75, + adm: '
Creative
', + crid: 'creative456', + dealid: 'deal789', + w: 300, + h: 250 + } + ], + seat: 'luponmedia' + } + ], + cur: 'USD' }; - let expectedResponse = [ + const bidRequests = [ { - 'requestId': '2a122246ef72ea', - 'cpm': '0.43', - 'width': 300, - 'height': 250, - 'creativeId': '443801010', - 'currency': 'USD', - 'dealId': '23425', - 'netRevenue': false, - 'ttl': 300, - 'referrer': '', - 'ad': ' ', - 'adomain': [ - 'mi.betrivers.com' - ], - 'meta': { - 'advertiserDomains': [ - 'mi.betrivers.com' - ] + bidId: 'bid456', + adUnitCode: 'test-div', + params: { keyId: 'uid_test_300_600' }, + mediaTypes: { banner: { sizes: [[300, 250]] } }, + ortb2: { + site: { + ref: 'https://mysite.com' + } } } ]; - let bidderRequest = { - 'data': '{"site":{"page":"https://novi.ba/clanak/176067/fast-car-beginner-s-guide-to-tuning-turbo-engines"}}' + const bidderRequest = { + refererInfo: { referer: 'https://mysite.com' } }; - let result = spec.interpretResponse({ body: response }, bidderRequest); - expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + const ortbRequest = converter.toORTB({ bidRequests, bidderRequest }); + + const result = spec.interpretResponse({ status: 200, body: response }, { data: ortbRequest }); + + expect(result[0].creativeId).to.equal('creative456'); + expect(result[0].dealId).to.equal('deal789'); + expect(result[0].referrer).to.equal('https://mysite.com'); }); - it('handles nobid responses', function () { - let noBidResponse = []; + it('should return empty array for unhandled response', function () { + const bidRequests = [{ + bidId: 'bad-response', + adUnitCode: 'test-div', + params: { keyId: 'uid_test_300_600' }, + mediaTypes: { banner: { sizes: [[300, 250]] } } + }]; + const ortbRequest = converter.toORTB({ bidRequests, bidderRequest: {} }); - let noBidBidderRequest = { - 'data': '{"site":{"page":""}}' - } - let noBidResult = spec.interpretResponse({ body: noBidResponse }, noBidBidderRequest); - expect(noBidResult.length).to.equal(0); + const result = spec.interpretResponse({ status: 400, body: {} }, { data: ortbRequest }); + expect(result).to.deep.equal([]); }); }); describe('getUserSyncs', function () { - const bidResponse1 = { - 'body': { - 'ext': { - 'responsetimemillis': { - 'luponmedia': 233 - }, - 'tmaxrequest': 1500, - 'usersyncs': { - 'status': 'ok', - 'bidder_status': [ + const bidResponse = { + body: { + ext: { + usersyncs: { + bidder_status: [ { - 'bidder': 'luponmedia', - 'no_cookie': true, - 'usersync': { - 'url': 'https://adxpremium.services/api/usersync', - 'type': 'redirect' - } + no_cookie: true, + usersync: { url: 'https://sync.img', type: 'image' } }, { - 'bidder': 'luponmedia', - 'no_cookie': true, - 'usersync': { - 'url': 'https://adxpremium.services/api/iframeusersync', - 'type': 'iframe' - } + no_cookie: true, + usersync: { url: 'https://sync.iframe', type: 'iframe' } } ] } @@ -299,101 +220,42 @@ describe('luponmediaBidAdapter', function () { } }; - const bidResponse2 = { - 'body': { - 'ext': { - 'responsetimemillis': { - 'luponmedia': 233 - }, - 'tmaxrequest': 1500, - 'usersyncs': { - 'status': 'no_cookie', - 'bidder_status': [] - } - } - } - }; - - it('should use a sync url from first response (pixel and iframe)', function () { - const syncs = spec.getUserSyncs({ pixelEnabled: true, iframeEnabled: true }, [bidResponse1, bidResponse2]); - expect(syncs).to.deep.equal([ - { - type: 'image', - url: 'https://adxpremium.services/api/usersync' - }, - { - type: 'iframe', - url: 'https://adxpremium.services/api/iframeusersync' - } - ]); - }); - - it('handle empty response (e.g. timeout)', function () { - const syncs = spec.getUserSyncs({ pixelEnabled: true, iframeEnabled: true }, []); - expect(syncs).to.deep.equal([]); + it('should return empty syncs when not pixel or iframe enabled', function () { + resetUserSync(); + const syncs = spec.getUserSyncs({ pixelEnabled: false, iframeEnabled: false }, [bidResponse]); + expect(syncs.length).to.equal(0); }); - it('returns empty syncs when not pixel enabled and not iframe enabled', function () { - const syncs = spec.getUserSyncs({ pixelEnabled: false, iframeEnabled: false }, [bidResponse1]); - expect(syncs).to.deep.equal([]); + it('returns pixel syncs when pixel enabled and iframe not enabled', function () { + resetUserSync(); + const syncs = spec.getUserSyncs({ pixelEnabled: true, iframeEnabled: false }, [bidResponse]); + expect(syncs).to.deep.include({ type: 'image', url: 'https://sync.img' }); }); - it('returns pixel syncs when pixel enabled and not iframe enabled', function() { + it('returns iframe syncs when iframe enabled and pixel not enabled', function () { resetUserSync(); - - const syncs = spec.getUserSyncs({ pixelEnabled: true, iframeEnabled: false }, [bidResponse1]); - expect(syncs).to.deep.equal([ - { - type: 'image', - url: 'https://adxpremium.services/api/usersync' - } - ]); + const syncs = spec.getUserSyncs({ pixelEnabled: false, iframeEnabled: true }, [bidResponse]); + expect(syncs).to.deep.include({ type: 'iframe', url: 'https://sync.iframe' }); }); - it('returns iframe syncs when not pixel enabled and iframe enabled', function() { + it('returns both syncs when both iframe and pixel enabled', function () { resetUserSync(); - - const syncs = spec.getUserSyncs({ pixelEnabled: false, iframeEnabled: true }, [bidResponse1]); - expect(syncs).to.deep.equal([ - { - type: 'iframe', - url: 'https://adxpremium.services/api/iframeusersync' - } + const syncs = spec.getUserSyncs({ pixelEnabled: true, iframeEnabled: true }, [bidResponse]); + expect(syncs).to.deep.include.members([ + { type: 'image', url: 'https://sync.img' }, + { type: 'iframe', url: 'https://sync.iframe' } ]); }); - }); - describe('hasValidSupplyChainParams', function () { - it('returns true if schain is valid', function () { - const schain = { - 'ver': '1.0', - 'complete': 1, - 'nodes': [ - { - 'asi': 'novi.ba', - 'sid': '199424', - 'hp': 1 - } - ] - }; - - const checkSchain = hasValidSupplyChainParams(schain); - expect(checkSchain).to.equal(true); + it('returns no syncs when usersyncs object missing', function () { + const emptyResponse = { body: { ext: {} } }; + const syncs = spec.getUserSyncs({ pixelEnabled: true, iframeEnabled: true }, [emptyResponse]); + expect(syncs).to.deep.equal([]); }); - it('returns false if schain is invalid', function () { - const schain = { - 'ver': '1.0', - 'complete': 1, - 'nodes': [ - { - 'invalid': 'novi.ba' - } - ] - }; - - const checkSchain = hasValidSupplyChainParams(schain); - expect(checkSchain).to.equal(false); + it('returns empty syncs on empty response array', function () { + const syncs = spec.getUserSyncs({ pixelEnabled: true, iframeEnabled: true }, []); + expect(syncs).to.deep.equal([]); }); }); });