From aa0c2de6d308ea0142828af836e6a63c4ee75f7e Mon Sep 17 00:00:00 2001 From: smartclip AdTechnology Date: Wed, 20 May 2020 16:34:34 +0200 Subject: [PATCH 01/29] Add smartclipBidAdapter --- modules/smartclipBidAdapter.js | 394 +++++++++++++++++++++++++++++++++ modules/smartclipBidAdapter.md | 51 +++++ 2 files changed, 445 insertions(+) create mode 100644 modules/smartclipBidAdapter.js create mode 100644 modules/smartclipBidAdapter.md diff --git a/modules/smartclipBidAdapter.js b/modules/smartclipBidAdapter.js new file mode 100644 index 00000000000..03c1231f128 --- /dev/null +++ b/modules/smartclipBidAdapter.js @@ -0,0 +1,394 @@ +import * as utils from 'src/utils'; +import { Renderer } from 'src/Renderer'; +import { registerBidder } from 'src/adapters/bidderFactory'; +import { VIDEO } from 'src/mediaTypes'; + +const BIDDER_CODE = 'smartx'; +const URL = '//bid.sxp.smartclip.net/bid/1000'; + +export const spec = { + code: BIDDER_CODE, + aliases: ['smartx'], + supportedMediaTypes: [VIDEO], + + /** + * Determines whether or not the given bid request is valid. + * From Prebid.js: isBidRequestValid - Verify the the AdUnits.bids, respond with true (valid) or false (invalid). + * + * @param {object} bid The bid to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function(bid) { + if (bid && typeof bid.params !== 'object') { + utils.logError(BIDDER_CODE + ': params is not defined or is incorrect in the bidder settings.'); + return false; + } + + if (!utils.deepAccess(bid, 'mediaTypes.video')) { + utils.logError(BIDDER_CODE + ': mediaTypes.video is not present in the bidder settings.'); + return false; + } + + const playerSize = utils.deepAccess(bid, 'mediaTypes.video.playerSize'); + if (!playerSize || !utils.isArray(playerSize)) { + utils.logError(BIDDER_CODE + ': mediaTypes.video.playerSize is not defined in the bidder settings.'); + return false; + } + + if (!utils.getBidIdParameter('tagId', bid.params)) { + utils.logError(BIDDER_CODE + ': tagId is not present in bidder params'); + return false; + } + + if (!utils.getBidIdParameter('publisherId', bid.params)) { + utils.logError(BIDDER_CODE + ': publisherId is not present in bidder params'); + return false; + } + + if (!utils.getBidIdParameter('siteId', bid.params)) { + utils.logError(BIDDER_CODE + ': siteId is not present in bidder params'); + return false; + } + + /* + if (!utils.getBidIdParameter('domain', bid.params)) { + utils.logError(BIDDER_CODE + ': domain is not present in bidder params'); + return false; + } + */ + + if (utils.deepAccess(bid, 'mediaTypes.video.context') == 'outstream' || utils.deepAccess(bid, 'params.ad_unit') == 'outstream') { + if (!utils.getBidIdParameter('outstream_function', bid.params)) { + if (!utils.getBidIdParameter('outstream_options', bid.params)) { + utils.logMessage(BIDDER_CODE + ': outstream_options parameter is not defined. The default outstream renderer will be injected in the header. You can override the default SmartX outstream rendering by defining your own Outstream function using field outstream_function.'); + return true; + } + if (!utils.getBidIdParameter('slot', bid.params.outstream_options)) { + utils.logMessage(BIDDER_CODE + ': slot parameter is not defined in outstream_options object in the configuration. The default outstream renderer will be injected in the header.'); + return true; + } + } + } + + return true; + }, + + /** + * Make a server request from the list of BidRequests. + * from Prebid.js: buildRequests - Takes an array of valid bid requests, all of which are guaranteed to have passed the isBidRequestValid() test. + * + * @param {BidRequest[]} bidRequests A non-empty list of bid requests which should be sent to the Server. + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function(bidRequests, bidderRequest) { + const page = bidderRequest.refererInfo.referer; + const isPageSecure = !!page.match(/^https:/) + //const domain = page.match(/:\/\/(.[^/]+)/)[1]; + + const smartxRequests = bidRequests.map(function(bid) { + const tagId = utils.getBidIdParameter('tagId', bid.params); + const publisherId = utils.getBidIdParameter('publisherId', bid.params); + const siteId = utils.getBidIdParameter('siteId', bid.params); + const domain = utils.getBidIdParameter('domain', bid.params); + const cat = utils.getBidIdParameter('cat', bid.params); + + let pubcid = null; + + const playerSize = utils.deepAccess(bid, 'mediaTypes.video.playerSize'); + const contentWidth = playerSize[0][0]; + const contentHeight = playerSize[0][1]; + + const secure = +(isPageSecure || (utils.getBidIdParameter('secure', bid.params) ? 1 : 0)); + + const ext = { + sdk_name: 'Prebid 1+' + }; + + const mimes = utils.getBidIdParameter('mimes', bid.params) || ['application/javascript', 'video/mp4', 'video/webm']; + const linearity = utils.getBidIdParameter('linearity', bid.params) || 1; + const minduration = utils.getBidIdParameter('minduration', bid.params) || 0; + const maxduration = utils.getBidIdParameter('maxduration', bid.params) || 500; + const startdelay = utils.getBidIdParameter('startdelay', bid.params) || 0; + const minbitrate = utils.getBidIdParameter('minbitrate', bid.params) || 0; + const maxbitrate = utils.getBidIdParameter('maxbitrate', bid.params) || 3500; + const delivery = utils.getBidIdParameter('delivery', bid.params) || [2]; + const pos = utils.getBidIdParameter('pos', bid.params) || 1; + const api = utils.getBidIdParameter('api', bid.params) || [2]; + const protocols = utils.getBidIdParameter('protocols', bid.params) || [2, 3, 5, 6]; + var contextcustom = utils.deepAccess(bid, 'mediaTypes.video.context'); + var placement = 1; + if (contextcustom == 'outstream') { + placement = 3; + } + + let smartxReq = { + id: bid.bidId, + secure: secure, + video: { + w: contentWidth, + h: contentHeight, + mimes: mimes, + linearity: linearity, + minduration: minduration, + maxduration: maxduration, + startdelay: startdelay, + protocols: protocols, + minbitrate: minbitrate, + maxbitrate: maxbitrate, + delivery: delivery, + pos: pos, + placement: placement, + api: api, + ext: ext + }, + tagid: tagId, + ext: { + 'smart.bidpricetype': 1 + } + }; + + if (utils.getBidIdParameter('bidfloor', bid.params) != '') { + smartxReq.bidfloor = utils.getBidIdParameter('bidfloor', bid.params); + } + + if (utils.getBidIdParameter('bidfloorcur', bid.params) != '') { + smartxReq.bidfloorcur = utils.getBidIdParameter('bidfloorcur', bid.params); + } + + if (bid.crumbs && bid.crumbs.pubcid) { + pubcid = bid.crumbs.pubcid; + } + + const language = navigator.language ? 'language' : 'userLanguage'; + const device = { + h: screen.height, + w: screen.width, + dnt: utils.getDNT() ? 1 : 0, + language: navigator[language].split('-')[0], + make: navigator.vendor ? navigator.vendor : '', + ua: navigator.userAgent + }; + + const at = utils.getBidIdParameter('at', bid.params) || 2; + const cur = utils.getBidIdParameter('cur', bid.params) || ['EUR']; + const requestPayload = { + id: utils.generateUUID(), + imp: smartxReq, + site: { + id: siteId, + page: page, + cat: cat, + content: 'content', + domain: domain, + publisher: { + id: publisherId + } + }, + device: device, + at: at, + cur: cur + }; + + const userExt = {}; + + // Add GDPR flag and consent string + if (bidderRequest && bidderRequest.gdprConsent) { + userExt.consent = bidderRequest.gdprConsent.consentString; + + if (typeof bidderRequest.gdprConsent.gdprApplies !== 'undefined') { + requestPayload.regs = { + ext: { + gdpr: (bidderRequest.gdprConsent.gdprApplies ? 1 : 0) + } + }; + } + } + + // Add common id if available + if (pubcid) { + userExt.fpc = pubcid; + } + + // Only add the user object if it's not empty + if (!utils.isEmpty(userExt)) { + requestPayload.user = { ext: userExt }; + } + + //CUSTOM - Emetriq Targeting + var isemq = (bid.params.user[0].data.name) || 'empty'; + if(isemq!=='empty'){ + var emqstring = (bid.params.user[0].data.segment[0].value) || 'empty'; + requestPayload.user = { + ext: userExt, + data: [{ + id: 'emq', + name:'emq', + segment: { + name : "emq", + value: emqstring, + } + }] + } + } + //CUSTOM - Emetriq Targeting + + + return { + method: 'POST', + url: URL, + data: requestPayload, + bidRequest: bidderRequest, + options: { + contentType: 'application/json', + customHeaders: { + 'x-openrtb-version': '2.3' + } + } + }; + }); + + return smartxRequests; + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {*} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function(serverResponse, bidderRequest) { + const bidResponses = []; + const serverResponseBody = serverResponse.body; + + if (serverResponseBody && utils.isArray(serverResponseBody.seatbid)) { + utils._each(serverResponseBody.seatbid, function(bids) { + utils._each(bids.bid, function(smartxBid) { + let currentBidRequest = {}; + for (let i in bidderRequest.bidRequest.bids) { + if (smartxBid.impid == bidderRequest.bidRequest.bids[i].bidId) { + currentBidRequest = bidderRequest.bidRequest.bids[i]; + } + } + + /** + * Make sure currency and price are the right ones + * TODO: what about the pre_market_bid partners sizes? + */ + utils._each(currentBidRequest.params.pre_market_bids, function(pmb) { + if (pmb.deal_id == smartxBid.id) { + smartxBid.price = pmb.price; + serverResponseBody.cur = pmb.currency; + } + }); + + const bid = { + requestId: currentBidRequest.bidId, + currency: serverResponseBody.cur || 'USD', + cpm: smartxBid.price, + creativeId: smartxBid.crid || '', + ttl: 360, + netRevenue: true, + vastContent: smartxBid.adm, + vastXml: smartxBid.adm, + mediaType: VIDEO, + width: smartxBid.w, + height: smartxBid.h + }; + + const context1 = utils.deepAccess(currentBidRequest, 'mediaTypes.video.context'); + const context2 = utils.deepAccess(currentBidRequest, 'params.ad_unit'); + if (context1 == 'outstream' || context2 == 'outstream') { + const playersize = utils.deepAccess(currentBidRequest, 'mediaTypes.video.playerSize'); + const renderer = Renderer.install({ + id: 0, + url: '//', + config: { + adText: 'SmartX Outstream Video Ad via Prebid.js', + player_width: playersize[0][0], + player_height: playersize[0][1], + content_page_url: utils.deepAccess(bidderRequest, 'data.site.page'), + ad_mute: +!!utils.deepAccess(currentBidRequest, 'params.ad_mute'), + hide_skin: +!!utils.deepAccess(currentBidRequest, 'params.hide_skin'), + outstream_options: utils.deepAccess(currentBidRequest, 'params.outstream_options'), + outstream_function: utils.deepAccess(currentBidRequest, 'params.outstream_function') + } + }); + + try { + renderer.setRender(outstreamRender); + renderer.setEventHandlers({ + impression: function impression() { + return utils.logMessage('SmartX outstream video impression event'); + }, + loaded: function loaded() { + return utils.logMessage('SmartX outstream video loaded event'); + }, + ended: function ended() { + utils.logMessage('SmartX outstream renderer video event'); + } + }); + } catch (err) { + utils.logWarn('Prebid Error calling setRender or setEve,tHandlers on renderer', err); + } + bid.renderer = renderer; + } + + bidResponses.push(bid); + }) + }); + } + + return bidResponses; + } +} + +function createOutstreamScript(bid) { + // const slot = utils.getBidIdParameter('slot', bid.renderer.config.outstream_options); + utils.logMessage('[SMARTX][renderer] Handle SmartX outstream renderer'); + + const elementId = bid.adUnitCode; + let smartPlayObj = { + minAdWidth: 290, + maxAdWidth: 900, + elementLocator: { allowInViewport: false, minimumElementWidth: 290, scanPixelsBelowViewport: 800 }, + onStartCallback: function(m, n) { try { sc_smartIntxtStart(n); } catch (f) {} }, + onCappedCallback: function(m, n) { try { sc_smartIntxtNoad(n); } catch (f) {} }, + onEndCallback: function(m, n) { try { sc_smartIntxtEnd(n); } catch (f) {} }, + debug: true + }; + + smartPlayObj.adResponse = bid.vastContent; + const script = window.document.createElement('script'); + script.type = 'text/javascript'; + script.async = 'true'; + script.src = '//dco.smartclip.net/?plc=7777777'; + script.onload = script.onreadystatechange = function() { + var rs = this.readyState; + if (rs && rs != 'complete' && rs != 'loaded') return; + try { + SmartPlay(elementId, smartPlayObj); + } catch (e) { console.warn('error caught : ' + e); } + }; + + return script; +} + +function outstreamRender(bid) { + const script = createOutstreamScript(bid); + if (bid.renderer.config.outstream_function != null && typeof bid.renderer.config.outstream_function === 'function') { + bid.renderer.config.outstream_function(bid, script); + } else { + try { + const slot = utils.getBidIdParameter('slot', bid.renderer.config.outstream_options); + if (slot && window.document.getElementById(slot)) { + window.document.getElementById(slot).appendChild(script); + } else { + window.document.getElementsByTagName('head')[0].appendChild(script); + } + } catch (err) { + utils.logError('[SMARTX][renderer] Error:' + err.message) + } + } +} + +registerBidder(spec); diff --git a/modules/smartclipBidAdapter.md b/modules/smartclipBidAdapter.md new file mode 100644 index 00000000000..42ae736650b --- /dev/null +++ b/modules/smartclipBidAdapter.md @@ -0,0 +1,51 @@ +Overview +Module Name: smartclip Bidder Adapter +Module Type: Bidder Adapter +Maintainer: adtech@smartclip.tv +Description +Connect to smartclip for bids. + +This adapter requires setup and approval from the smartclip team. + +Test Parameters - Use case #1 - outstream with default rendering options + +var adUnits = [{ + code: 'video1', + mediaTypes: { + banner: { + sizes: sizes + }, + video: { + context: 'outstream', + playerSize: [640, 480] + } + }, + bids: [{ + bidder: 'smartclip', + params: { + tagId: 'Nu68JuOWAvrbzoyrOR9a7A', + publisherId: 'pubid', + siteId: 'siteId', + cat: "category", + bidfloor: 0.3, + bidfloorcur: "EUR", + at: 2, + cur: ["EUR"], + outstream_options: { + slot: 'video1' + }, + user: [{ + data: { + id: 'emq', + name: 'emq', + segment: [{ + id: 'emq', + name:'emq', + value: 'e0:k14' + }] + } + }] + //outstream_function: myOutstreamFunction + } + }], + }]; \ No newline at end of file From a4daadc9450a813fc58d37ab129a09a628d5d9f0 Mon Sep 17 00:00:00 2001 From: Gino Date: Wed, 2 Sep 2020 12:57:48 +0200 Subject: [PATCH 02/29] smartxBidAdapter.js - removed unused variables, removed debug, added window before the outstream related functions --- modules/smartxBidAdapter.js | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/modules/smartxBidAdapter.js b/modules/smartxBidAdapter.js index a745c54e39c..4409e4e9dfb 100644 --- a/modules/smartxBidAdapter.js +++ b/modules/smartxBidAdapter.js @@ -331,13 +331,6 @@ function createOutstreamScript(bid) { // const slot = utils.getBidIdParameter('slot', bid.renderer.config.outstream_options); utils.logMessage('[SMARTX][renderer] Handle SmartX outstream renderer'); const elementId = bid.adUnitCode; - // eslint-disable-next-line camelcase - var sc_smartIntxtStart; - // eslint-disable-next-line camelcase - var sc_smartIntxtNoad; - // eslint-disable-next-line camelcase - var sc_smartIntxtEnd; - var SmartPlay; let smartPlayObj = { minAdWidth: 290, maxAdWidth: 900, @@ -348,20 +341,19 @@ function createOutstreamScript(bid) { }, onStartCallback: function (m, n) { try { - sc_smartIntxtStart(n); + window.sc_smartIntxtStart(n); } catch (f) {} }, onCappedCallback: function (m, n) { try { - sc_smartIntxtNoad(n); + window.sc_smartIntxtNoad(n); } catch (f) {} }, onEndCallback: function (m, n) { try { - sc_smartIntxtEnd(n); + window.sc_smartIntxtEnd(n); } catch (f) {} }, - debug: true }; smartPlayObj.adResponse = bid.vastContent; const script = window.document.createElement('script'); @@ -372,7 +364,7 @@ function createOutstreamScript(bid) { var rs = this.readyState; if (rs && rs != 'complete' && rs != 'loaded') return; try { - SmartPlay(elementId, smartPlayObj); + window.SmartPlay(elementId, smartPlayObj); } catch (e) { utils.logError('error caught : ' + e); } From 2a965c9a5e31535d4150344d00207d8e8956a063 Mon Sep 17 00:00:00 2001 From: Gino Cirlini Date: Fri, 29 Jan 2021 11:46:27 +0100 Subject: [PATCH 03/29] - made outstream player configurable --- modules/smartxBidAdapter.js | 50 +++++++++++++++++++++++++++++++------ modules/smartxBidAdapter.md | 34 +++++++++++++++++-------- 2 files changed, 66 insertions(+), 18 deletions(-) diff --git a/modules/smartxBidAdapter.js b/modules/smartxBidAdapter.js index 4409e4e9dfb..b2381c41d8e 100644 --- a/modules/smartxBidAdapter.js +++ b/modules/smartxBidAdapter.js @@ -328,16 +328,49 @@ export const spec = { } function createOutstreamScript(bid) { - // const slot = utils.getBidIdParameter('slot', bid.renderer.config.outstream_options); + // for SmartPlay 4.12 + function sc_prebidClose(ele, completeCollapsed) { + if (completeCollapsed) { + document.getElementById(ele.id).style.display = 'none'; + } + } + + const confMinAdWidth = bid.getBidIdParameter('minAdWidth', bid.renderer.config.outstream_options) || 290; + const confMaxAdWidth = bid.getBidIdParameter('maxAdWidth', bid.renderer.config.outstream_options) || 900; + const confStartOpen = bid.getBidIdParameter('startOpen', bid.renderer.config.outstream_options) || 'false'; + const confEndingScreen = bid.getBidIdParameter('endingScreen', bid.renderer.config.outstream_options) || 'true'; + const confHeaderText = bid.getBidIdParameter('headerText', bid.renderer.config.outstream_options) || ''; + const confSkipOffset = bid.getBidIdParameter('skipOffset', bid.renderer.config.outstream_options) || 0; + const confDesiredBitrate = bid.getBidIdParameter('desiredBitrate', bid.renderer.config.outstream_options) || 1600; + const elementId = utils.getBidIdParameter('slot', bid.renderer.config.outstream_options) || bid.adUnitCode; + + // for SmartPlay 4.12 + let initCollapsed = true; + let completeCollapsed = true; + if (confStartOpen === 'true') { + initCollapsed = false; + } + if (confEndingScreen === 'true') { + completeCollapsed = false; + } + utils.logMessage('[SMARTX][renderer] Handle SmartX outstream renderer'); - const elementId = bid.adUnitCode; + let smartPlayObj = { - minAdWidth: 290, - maxAdWidth: 900, - elementLocator: { - allowInViewport: false, - minimumElementWidth: 290, - scanPixelsBelowViewport: 800 + minAdWidth: confMinAdWidth, + maxAdWidth: confMaxAdWidth, + headerText: confHeaderText, + skipOffset: confSkipOffset, + behaviourMatrix: { + init: { + 'collapsed': initCollapsed + }, + complete: { + 'collapsed': completeCollapsed + } + }, + environmentVars: { + desiredBitrate: confDesiredBitrate, }, onStartCallback: function (m, n) { try { @@ -351,6 +384,7 @@ function createOutstreamScript(bid) { }, onEndCallback: function (m, n) { try { + sc_prebidClose(n, completeCollapsed); // for SmartPlay 4.12 window.sc_smartIntxtEnd(n); } catch (f) {} }, diff --git a/modules/smartxBidAdapter.md b/modules/smartxBidAdapter.md index a53af839e2b..082a36f3dde 100644 --- a/modules/smartxBidAdapter.md +++ b/modules/smartxBidAdapter.md @@ -29,11 +29,18 @@ This adapter requires setup and approval from the smartclip team. publisherId: '11986', siteId: '22860', bidfloor: 0.3, - bidfloorcur: "EUR", + bidfloorcur: 'EUR', at: 2, - cur: ["EUR"], + cur: ['EUR'], outstream_options: { - slot: 'video1' + slot: 'video1', + minAdWidth: 290, + maxAdWidth: 900, + headerText: '', + skipOffset: 0, + startOpen: 'true', + endingScreen: 'true', + desiredBitrate: 1600, }, } }], @@ -57,11 +64,18 @@ This adapter requires setup and approval from the smartclip team. publisherId: '11986', siteId: '22860', bidfloor: 0.3, - bidfloorcur: "EUR", + bidfloorcur: 'EUR', at: 2, - cur: ["EUR"], + cur: ['EUR'], outstream_options: { - slot: 'video1' + slot: 'video1', + minAdWidth: 290, + maxAdWidth: 900, + headerText: '', + skipOffset: 0, + startOpen: 'true', + endingScreen: 'true', + desiredBitrate: 1600, }, user: { data: [{ @@ -104,9 +118,9 @@ This adapter requires setup and approval from the smartclip team. publisherId: '11986', siteId: '22860', bidfloor: 0.3, - bidfloorcur: "EUR", + bidfloorcur: 'EUR', at: 2, - cur: ["EUR"] + cur: ['EUR'] } }], }]; @@ -129,9 +143,9 @@ This adapter requires setup and approval from the smartclip team. publisherId: '11986', siteId: '22860', bidfloor: 0.3, - bidfloorcur: "EUR", + bidfloorcur: 'EUR', at: 2, - cur: ["EUR"], + cur: ['EUR'], user: { data: [{ id: 'emq', From b577bd3f602cb4a9fa68af0b23a5f59e1b72b4d4 Mon Sep 17 00:00:00 2001 From: Gino Cirlini Date: Fri, 29 Jan 2021 12:11:19 +0100 Subject: [PATCH 04/29] remove wrong named files --- modules/smartclipBidAdapter.js | 394 --------------------------------- modules/smartclipBidAdapter.md | 51 ----- 2 files changed, 445 deletions(-) delete mode 100644 modules/smartclipBidAdapter.js delete mode 100644 modules/smartclipBidAdapter.md diff --git a/modules/smartclipBidAdapter.js b/modules/smartclipBidAdapter.js deleted file mode 100644 index 03c1231f128..00000000000 --- a/modules/smartclipBidAdapter.js +++ /dev/null @@ -1,394 +0,0 @@ -import * as utils from 'src/utils'; -import { Renderer } from 'src/Renderer'; -import { registerBidder } from 'src/adapters/bidderFactory'; -import { VIDEO } from 'src/mediaTypes'; - -const BIDDER_CODE = 'smartx'; -const URL = '//bid.sxp.smartclip.net/bid/1000'; - -export const spec = { - code: BIDDER_CODE, - aliases: ['smartx'], - supportedMediaTypes: [VIDEO], - - /** - * Determines whether or not the given bid request is valid. - * From Prebid.js: isBidRequestValid - Verify the the AdUnits.bids, respond with true (valid) or false (invalid). - * - * @param {object} bid The bid to validate. - * @return boolean True if this is a valid bid, and false otherwise. - */ - isBidRequestValid: function(bid) { - if (bid && typeof bid.params !== 'object') { - utils.logError(BIDDER_CODE + ': params is not defined or is incorrect in the bidder settings.'); - return false; - } - - if (!utils.deepAccess(bid, 'mediaTypes.video')) { - utils.logError(BIDDER_CODE + ': mediaTypes.video is not present in the bidder settings.'); - return false; - } - - const playerSize = utils.deepAccess(bid, 'mediaTypes.video.playerSize'); - if (!playerSize || !utils.isArray(playerSize)) { - utils.logError(BIDDER_CODE + ': mediaTypes.video.playerSize is not defined in the bidder settings.'); - return false; - } - - if (!utils.getBidIdParameter('tagId', bid.params)) { - utils.logError(BIDDER_CODE + ': tagId is not present in bidder params'); - return false; - } - - if (!utils.getBidIdParameter('publisherId', bid.params)) { - utils.logError(BIDDER_CODE + ': publisherId is not present in bidder params'); - return false; - } - - if (!utils.getBidIdParameter('siteId', bid.params)) { - utils.logError(BIDDER_CODE + ': siteId is not present in bidder params'); - return false; - } - - /* - if (!utils.getBidIdParameter('domain', bid.params)) { - utils.logError(BIDDER_CODE + ': domain is not present in bidder params'); - return false; - } - */ - - if (utils.deepAccess(bid, 'mediaTypes.video.context') == 'outstream' || utils.deepAccess(bid, 'params.ad_unit') == 'outstream') { - if (!utils.getBidIdParameter('outstream_function', bid.params)) { - if (!utils.getBidIdParameter('outstream_options', bid.params)) { - utils.logMessage(BIDDER_CODE + ': outstream_options parameter is not defined. The default outstream renderer will be injected in the header. You can override the default SmartX outstream rendering by defining your own Outstream function using field outstream_function.'); - return true; - } - if (!utils.getBidIdParameter('slot', bid.params.outstream_options)) { - utils.logMessage(BIDDER_CODE + ': slot parameter is not defined in outstream_options object in the configuration. The default outstream renderer will be injected in the header.'); - return true; - } - } - } - - return true; - }, - - /** - * Make a server request from the list of BidRequests. - * from Prebid.js: buildRequests - Takes an array of valid bid requests, all of which are guaranteed to have passed the isBidRequestValid() test. - * - * @param {BidRequest[]} bidRequests A non-empty list of bid requests which should be sent to the Server. - * @return ServerRequest Info describing the request to the server. - */ - buildRequests: function(bidRequests, bidderRequest) { - const page = bidderRequest.refererInfo.referer; - const isPageSecure = !!page.match(/^https:/) - //const domain = page.match(/:\/\/(.[^/]+)/)[1]; - - const smartxRequests = bidRequests.map(function(bid) { - const tagId = utils.getBidIdParameter('tagId', bid.params); - const publisherId = utils.getBidIdParameter('publisherId', bid.params); - const siteId = utils.getBidIdParameter('siteId', bid.params); - const domain = utils.getBidIdParameter('domain', bid.params); - const cat = utils.getBidIdParameter('cat', bid.params); - - let pubcid = null; - - const playerSize = utils.deepAccess(bid, 'mediaTypes.video.playerSize'); - const contentWidth = playerSize[0][0]; - const contentHeight = playerSize[0][1]; - - const secure = +(isPageSecure || (utils.getBidIdParameter('secure', bid.params) ? 1 : 0)); - - const ext = { - sdk_name: 'Prebid 1+' - }; - - const mimes = utils.getBidIdParameter('mimes', bid.params) || ['application/javascript', 'video/mp4', 'video/webm']; - const linearity = utils.getBidIdParameter('linearity', bid.params) || 1; - const minduration = utils.getBidIdParameter('minduration', bid.params) || 0; - const maxduration = utils.getBidIdParameter('maxduration', bid.params) || 500; - const startdelay = utils.getBidIdParameter('startdelay', bid.params) || 0; - const minbitrate = utils.getBidIdParameter('minbitrate', bid.params) || 0; - const maxbitrate = utils.getBidIdParameter('maxbitrate', bid.params) || 3500; - const delivery = utils.getBidIdParameter('delivery', bid.params) || [2]; - const pos = utils.getBidIdParameter('pos', bid.params) || 1; - const api = utils.getBidIdParameter('api', bid.params) || [2]; - const protocols = utils.getBidIdParameter('protocols', bid.params) || [2, 3, 5, 6]; - var contextcustom = utils.deepAccess(bid, 'mediaTypes.video.context'); - var placement = 1; - if (contextcustom == 'outstream') { - placement = 3; - } - - let smartxReq = { - id: bid.bidId, - secure: secure, - video: { - w: contentWidth, - h: contentHeight, - mimes: mimes, - linearity: linearity, - minduration: minduration, - maxduration: maxduration, - startdelay: startdelay, - protocols: protocols, - minbitrate: minbitrate, - maxbitrate: maxbitrate, - delivery: delivery, - pos: pos, - placement: placement, - api: api, - ext: ext - }, - tagid: tagId, - ext: { - 'smart.bidpricetype': 1 - } - }; - - if (utils.getBidIdParameter('bidfloor', bid.params) != '') { - smartxReq.bidfloor = utils.getBidIdParameter('bidfloor', bid.params); - } - - if (utils.getBidIdParameter('bidfloorcur', bid.params) != '') { - smartxReq.bidfloorcur = utils.getBidIdParameter('bidfloorcur', bid.params); - } - - if (bid.crumbs && bid.crumbs.pubcid) { - pubcid = bid.crumbs.pubcid; - } - - const language = navigator.language ? 'language' : 'userLanguage'; - const device = { - h: screen.height, - w: screen.width, - dnt: utils.getDNT() ? 1 : 0, - language: navigator[language].split('-')[0], - make: navigator.vendor ? navigator.vendor : '', - ua: navigator.userAgent - }; - - const at = utils.getBidIdParameter('at', bid.params) || 2; - const cur = utils.getBidIdParameter('cur', bid.params) || ['EUR']; - const requestPayload = { - id: utils.generateUUID(), - imp: smartxReq, - site: { - id: siteId, - page: page, - cat: cat, - content: 'content', - domain: domain, - publisher: { - id: publisherId - } - }, - device: device, - at: at, - cur: cur - }; - - const userExt = {}; - - // Add GDPR flag and consent string - if (bidderRequest && bidderRequest.gdprConsent) { - userExt.consent = bidderRequest.gdprConsent.consentString; - - if (typeof bidderRequest.gdprConsent.gdprApplies !== 'undefined') { - requestPayload.regs = { - ext: { - gdpr: (bidderRequest.gdprConsent.gdprApplies ? 1 : 0) - } - }; - } - } - - // Add common id if available - if (pubcid) { - userExt.fpc = pubcid; - } - - // Only add the user object if it's not empty - if (!utils.isEmpty(userExt)) { - requestPayload.user = { ext: userExt }; - } - - //CUSTOM - Emetriq Targeting - var isemq = (bid.params.user[0].data.name) || 'empty'; - if(isemq!=='empty'){ - var emqstring = (bid.params.user[0].data.segment[0].value) || 'empty'; - requestPayload.user = { - ext: userExt, - data: [{ - id: 'emq', - name:'emq', - segment: { - name : "emq", - value: emqstring, - } - }] - } - } - //CUSTOM - Emetriq Targeting - - - return { - method: 'POST', - url: URL, - data: requestPayload, - bidRequest: bidderRequest, - options: { - contentType: 'application/json', - customHeaders: { - 'x-openrtb-version': '2.3' - } - } - }; - }); - - return smartxRequests; - }, - - /** - * Unpack the response from the server into a list of bids. - * - * @param {*} serverResponse A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ - interpretResponse: function(serverResponse, bidderRequest) { - const bidResponses = []; - const serverResponseBody = serverResponse.body; - - if (serverResponseBody && utils.isArray(serverResponseBody.seatbid)) { - utils._each(serverResponseBody.seatbid, function(bids) { - utils._each(bids.bid, function(smartxBid) { - let currentBidRequest = {}; - for (let i in bidderRequest.bidRequest.bids) { - if (smartxBid.impid == bidderRequest.bidRequest.bids[i].bidId) { - currentBidRequest = bidderRequest.bidRequest.bids[i]; - } - } - - /** - * Make sure currency and price are the right ones - * TODO: what about the pre_market_bid partners sizes? - */ - utils._each(currentBidRequest.params.pre_market_bids, function(pmb) { - if (pmb.deal_id == smartxBid.id) { - smartxBid.price = pmb.price; - serverResponseBody.cur = pmb.currency; - } - }); - - const bid = { - requestId: currentBidRequest.bidId, - currency: serverResponseBody.cur || 'USD', - cpm: smartxBid.price, - creativeId: smartxBid.crid || '', - ttl: 360, - netRevenue: true, - vastContent: smartxBid.adm, - vastXml: smartxBid.adm, - mediaType: VIDEO, - width: smartxBid.w, - height: smartxBid.h - }; - - const context1 = utils.deepAccess(currentBidRequest, 'mediaTypes.video.context'); - const context2 = utils.deepAccess(currentBidRequest, 'params.ad_unit'); - if (context1 == 'outstream' || context2 == 'outstream') { - const playersize = utils.deepAccess(currentBidRequest, 'mediaTypes.video.playerSize'); - const renderer = Renderer.install({ - id: 0, - url: '//', - config: { - adText: 'SmartX Outstream Video Ad via Prebid.js', - player_width: playersize[0][0], - player_height: playersize[0][1], - content_page_url: utils.deepAccess(bidderRequest, 'data.site.page'), - ad_mute: +!!utils.deepAccess(currentBidRequest, 'params.ad_mute'), - hide_skin: +!!utils.deepAccess(currentBidRequest, 'params.hide_skin'), - outstream_options: utils.deepAccess(currentBidRequest, 'params.outstream_options'), - outstream_function: utils.deepAccess(currentBidRequest, 'params.outstream_function') - } - }); - - try { - renderer.setRender(outstreamRender); - renderer.setEventHandlers({ - impression: function impression() { - return utils.logMessage('SmartX outstream video impression event'); - }, - loaded: function loaded() { - return utils.logMessage('SmartX outstream video loaded event'); - }, - ended: function ended() { - utils.logMessage('SmartX outstream renderer video event'); - } - }); - } catch (err) { - utils.logWarn('Prebid Error calling setRender or setEve,tHandlers on renderer', err); - } - bid.renderer = renderer; - } - - bidResponses.push(bid); - }) - }); - } - - return bidResponses; - } -} - -function createOutstreamScript(bid) { - // const slot = utils.getBidIdParameter('slot', bid.renderer.config.outstream_options); - utils.logMessage('[SMARTX][renderer] Handle SmartX outstream renderer'); - - const elementId = bid.adUnitCode; - let smartPlayObj = { - minAdWidth: 290, - maxAdWidth: 900, - elementLocator: { allowInViewport: false, minimumElementWidth: 290, scanPixelsBelowViewport: 800 }, - onStartCallback: function(m, n) { try { sc_smartIntxtStart(n); } catch (f) {} }, - onCappedCallback: function(m, n) { try { sc_smartIntxtNoad(n); } catch (f) {} }, - onEndCallback: function(m, n) { try { sc_smartIntxtEnd(n); } catch (f) {} }, - debug: true - }; - - smartPlayObj.adResponse = bid.vastContent; - const script = window.document.createElement('script'); - script.type = 'text/javascript'; - script.async = 'true'; - script.src = '//dco.smartclip.net/?plc=7777777'; - script.onload = script.onreadystatechange = function() { - var rs = this.readyState; - if (rs && rs != 'complete' && rs != 'loaded') return; - try { - SmartPlay(elementId, smartPlayObj); - } catch (e) { console.warn('error caught : ' + e); } - }; - - return script; -} - -function outstreamRender(bid) { - const script = createOutstreamScript(bid); - if (bid.renderer.config.outstream_function != null && typeof bid.renderer.config.outstream_function === 'function') { - bid.renderer.config.outstream_function(bid, script); - } else { - try { - const slot = utils.getBidIdParameter('slot', bid.renderer.config.outstream_options); - if (slot && window.document.getElementById(slot)) { - window.document.getElementById(slot).appendChild(script); - } else { - window.document.getElementsByTagName('head')[0].appendChild(script); - } - } catch (err) { - utils.logError('[SMARTX][renderer] Error:' + err.message) - } - } -} - -registerBidder(spec); diff --git a/modules/smartclipBidAdapter.md b/modules/smartclipBidAdapter.md deleted file mode 100644 index 42ae736650b..00000000000 --- a/modules/smartclipBidAdapter.md +++ /dev/null @@ -1,51 +0,0 @@ -Overview -Module Name: smartclip Bidder Adapter -Module Type: Bidder Adapter -Maintainer: adtech@smartclip.tv -Description -Connect to smartclip for bids. - -This adapter requires setup and approval from the smartclip team. - -Test Parameters - Use case #1 - outstream with default rendering options - -var adUnits = [{ - code: 'video1', - mediaTypes: { - banner: { - sizes: sizes - }, - video: { - context: 'outstream', - playerSize: [640, 480] - } - }, - bids: [{ - bidder: 'smartclip', - params: { - tagId: 'Nu68JuOWAvrbzoyrOR9a7A', - publisherId: 'pubid', - siteId: 'siteId', - cat: "category", - bidfloor: 0.3, - bidfloorcur: "EUR", - at: 2, - cur: ["EUR"], - outstream_options: { - slot: 'video1' - }, - user: [{ - data: { - id: 'emq', - name: 'emq', - segment: [{ - id: 'emq', - name:'emq', - value: 'e0:k14' - }] - } - }] - //outstream_function: myOutstreamFunction - } - }], - }]; \ No newline at end of file From 2b96ef1e9498073f54b798080ef84b87ae28d347 Mon Sep 17 00:00:00 2001 From: Gino Cirlini Date: Fri, 29 Jan 2021 12:53:17 +0100 Subject: [PATCH 05/29] camelcase --- modules/smartxBidAdapter.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/smartxBidAdapter.js b/modules/smartxBidAdapter.js index b2381c41d8e..aef90350924 100644 --- a/modules/smartxBidAdapter.js +++ b/modules/smartxBidAdapter.js @@ -329,7 +329,7 @@ export const spec = { function createOutstreamScript(bid) { // for SmartPlay 4.12 - function sc_prebidClose(ele, completeCollapsed) { + function scPrebidClose(ele, completeCollapsed) { if (completeCollapsed) { document.getElementById(ele.id).style.display = 'none'; } @@ -384,7 +384,7 @@ function createOutstreamScript(bid) { }, onEndCallback: function (m, n) { try { - sc_prebidClose(n, completeCollapsed); // for SmartPlay 4.12 + scPrebidClose(n, completeCollapsed); // for SmartPlay 4.12 window.sc_smartIntxtEnd(n); } catch (f) {} }, From 871582019d4ccd0081998e137a8e36e34d923d4b Mon Sep 17 00:00:00 2001 From: Gino Cirlini Date: Wed, 3 Feb 2021 10:45:25 +0100 Subject: [PATCH 06/29] fix --- modules/smartxBidAdapter.js | 14 +++++++------- test/spec/modules/smartxBidAdapter_spec.js | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/modules/smartxBidAdapter.js b/modules/smartxBidAdapter.js index aef90350924..804b25d1afc 100644 --- a/modules/smartxBidAdapter.js +++ b/modules/smartxBidAdapter.js @@ -335,13 +335,13 @@ function createOutstreamScript(bid) { } } - const confMinAdWidth = bid.getBidIdParameter('minAdWidth', bid.renderer.config.outstream_options) || 290; - const confMaxAdWidth = bid.getBidIdParameter('maxAdWidth', bid.renderer.config.outstream_options) || 900; - const confStartOpen = bid.getBidIdParameter('startOpen', bid.renderer.config.outstream_options) || 'false'; - const confEndingScreen = bid.getBidIdParameter('endingScreen', bid.renderer.config.outstream_options) || 'true'; - const confHeaderText = bid.getBidIdParameter('headerText', bid.renderer.config.outstream_options) || ''; - const confSkipOffset = bid.getBidIdParameter('skipOffset', bid.renderer.config.outstream_options) || 0; - const confDesiredBitrate = bid.getBidIdParameter('desiredBitrate', bid.renderer.config.outstream_options) || 1600; + const confMinAdWidth = utils.getBidIdParameter('minAdWidth', bid.renderer.config.outstream_options) || 290; + const confMaxAdWidth = utils.getBidIdParameter('maxAdWidth', bid.renderer.config.outstream_options) || 900; + const confStartOpen = utils.getBidIdParameter('startOpen', bid.renderer.config.outstream_options) || 'false'; + const confEndingScreen = utils.getBidIdParameter('endingScreen', bid.renderer.config.outstream_options) || 'true'; + const confHeaderText = utils.getBidIdParameter('headerText', bid.renderer.config.outstream_options) || ''; + const confSkipOffset = utils.getBidIdParameter('skipOffset', bid.renderer.config.outstream_options) || 0; + const confDesiredBitrate = utils.getBidIdParameter('desiredBitrate', bid.renderer.config.outstream_options) || 1600; const elementId = utils.getBidIdParameter('slot', bid.renderer.config.outstream_options) || bid.adUnitCode; // for SmartPlay 4.12 diff --git a/test/spec/modules/smartxBidAdapter_spec.js b/test/spec/modules/smartxBidAdapter_spec.js index efc6abcc5fa..82c6642bd74 100644 --- a/test/spec/modules/smartxBidAdapter_spec.js +++ b/test/spec/modules/smartxBidAdapter_spec.js @@ -493,7 +493,7 @@ describe('The smartx adapter', function () { }; }); - it('should attempt to insert the EASI script', function () { + it('should attempt to insert the script', function () { var scriptTag; sinon.stub(window.document, 'getElementById').returns({ appendChild: sinon.stub().callsFake(function (script) { From b55fb7dd9d0d9d641e3008be9046de25f98104ab Mon Sep 17 00:00:00 2001 From: Gino Cirlini Date: Mon, 1 Mar 2021 15:34:26 +0100 Subject: [PATCH 07/29] Out-Stream render update to SmartPlay 5.2 --- modules/smartxBidAdapter.js | 92 ++++++++++++++++--------------------- 1 file changed, 39 insertions(+), 53 deletions(-) diff --git a/modules/smartxBidAdapter.js b/modules/smartxBidAdapter.js index 804b25d1afc..e9d3f777616 100644 --- a/modules/smartxBidAdapter.js +++ b/modules/smartxBidAdapter.js @@ -328,50 +328,25 @@ export const spec = { } function createOutstreamScript(bid) { - // for SmartPlay 4.12 - function scPrebidClose(ele, completeCollapsed) { - if (completeCollapsed) { - document.getElementById(ele.id).style.display = 'none'; - } - } - const confMinAdWidth = utils.getBidIdParameter('minAdWidth', bid.renderer.config.outstream_options) || 290; const confMaxAdWidth = utils.getBidIdParameter('maxAdWidth', bid.renderer.config.outstream_options) || 900; - const confStartOpen = utils.getBidIdParameter('startOpen', bid.renderer.config.outstream_options) || 'false'; - const confEndingScreen = utils.getBidIdParameter('endingScreen', bid.renderer.config.outstream_options) || 'true'; - const confHeaderText = utils.getBidIdParameter('headerText', bid.renderer.config.outstream_options) || ''; - const confSkipOffset = utils.getBidIdParameter('skipOffset', bid.renderer.config.outstream_options) || 0; - const confDesiredBitrate = utils.getBidIdParameter('desiredBitrate', bid.renderer.config.outstream_options) || 1600; + const confStartOpen = utils.getBidIdParameter('startOpen', bid.renderer.config.outstream_options); + const confEndingScreen = utils.getBidIdParameter('endingScreen', bid.renderer.config.outstream_options); + const confTitle = utils.getBidIdParameter('title', bid.renderer.config.outstream_options); + const confSkipOffset = utils.getBidIdParameter('skipOffset', bid.renderer.config.outstream_options); + const confDesiredBitrate = utils.getBidIdParameter('desiredBitrate', bid.renderer.config.outstream_options); const elementId = utils.getBidIdParameter('slot', bid.renderer.config.outstream_options) || bid.adUnitCode; - // for SmartPlay 4.12 - let initCollapsed = true; - let completeCollapsed = true; - if (confStartOpen === 'true') { - initCollapsed = false; - } - if (confEndingScreen === 'true') { - completeCollapsed = false; - } - utils.logMessage('[SMARTX][renderer] Handle SmartX outstream renderer'); - let smartPlayObj = { + var smartPlayObj = { minAdWidth: confMinAdWidth, maxAdWidth: confMaxAdWidth, - headerText: confHeaderText, + title: confTitle, skipOffset: confSkipOffset, - behaviourMatrix: { - init: { - 'collapsed': initCollapsed - }, - complete: { - 'collapsed': completeCollapsed - } - }, - environmentVars: { - desiredBitrate: confDesiredBitrate, - }, + startOpen: confStartOpen, + endingScreen: confEndingScreen, + desiredBitrate: confDesiredBitrate, onStartCallback: function (m, n) { try { window.sc_smartIntxtStart(n); @@ -384,25 +359,40 @@ function createOutstreamScript(bid) { }, onEndCallback: function (m, n) { try { - scPrebidClose(n, completeCollapsed); // for SmartPlay 4.12 window.sc_smartIntxtEnd(n); } catch (f) {} }, }; + smartPlayObj.adResponse = bid.vastContent; - const script = window.document.createElement('script'); - script.type = 'text/javascript'; - script.async = 'true'; - script.src = 'https://dco.smartclip.net/?plc=7777777'; - script.onload = script.onreadystatechange = function () { - var rs = this.readyState; - if (rs && rs != 'complete' && rs != 'loaded') return; + + let script = ''; + let divID = '#' + elementId; + + var insertJSNode = function (url, nodeId = null, fun = null) { + script = document.createElement('script'); + script.type = 'text/javascript'; + if (nodeId != null) { + script.id = nodeId; + } + if (url != '') { + script.async = 'true'; + script.src = url; + if (fun != null) { + script.onload = script.onreadystatechange = fun; + } + } else { + script.text = 'var x=' + fun + '();'; + } + } + + insertJSNode('//dco.smartclip.net/?plc=7777778', null, function () { try { - window.SmartPlay(elementId, smartPlayObj); + let _outstreamPlayer = new OutstreamPlayer(divID, smartPlayObj) } catch (e) { - utils.logError('error caught : ' + e); + utils.logError('[SmartPlay][renderer] Error caught: ' + e); } - }; + }) return script; } @@ -412,15 +402,11 @@ function outstreamRender(bid) { bid.renderer.config.outstream_function(bid, script); } else { try { - const slot = utils.getBidIdParameter('slot', bid.renderer.config.outstream_options); - if (slot && window.document.getElementById(slot)) { - window.document.getElementById(slot).appendChild(script); - } else { - window.document.getElementsByTagName('head')[0].appendChild(script); - } + const firstScriptTagOnSite = window.document.getElementsByTagName('script')[0]; + firstScriptTagOnSite.parentNode.insertBefore(script, firstScriptTagOnSite); } catch (err) { utils.logError('[SMARTX][renderer] Error:' + err.message) } } } -registerBidder(spec); +registerBidder(spec); \ No newline at end of file From 1af62e69620c7649ef0964aa6e6d45dec5635d9a Mon Sep 17 00:00:00 2001 From: Gino Cirlini Date: Mon, 1 Mar 2021 16:04:18 +0100 Subject: [PATCH 08/29] ESlint fix --- modules/smartxBidAdapter.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/smartxBidAdapter.js b/modules/smartxBidAdapter.js index e9d3f777616..084438e50dc 100644 --- a/modules/smartxBidAdapter.js +++ b/modules/smartxBidAdapter.js @@ -387,7 +387,8 @@ function createOutstreamScript(bid) { } insertJSNode('//dco.smartclip.net/?plc=7777778', null, function () { - try { + try { + // eslint-disable-next-line no-unused-vars no-undef let _outstreamPlayer = new OutstreamPlayer(divID, smartPlayObj) } catch (e) { utils.logError('[SmartPlay][renderer] Error caught: ' + e); @@ -409,4 +410,4 @@ function outstreamRender(bid) { } } } -registerBidder(spec); \ No newline at end of file +registerBidder(spec); From 1ef8d74c82bfccb0f5b560b55275c725af8198bd Mon Sep 17 00:00:00 2001 From: Gino Cirlini Date: Mon, 1 Mar 2021 16:13:22 +0100 Subject: [PATCH 09/29] ESlint fix --- modules/smartxBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/smartxBidAdapter.js b/modules/smartxBidAdapter.js index 084438e50dc..3589dc4a573 100644 --- a/modules/smartxBidAdapter.js +++ b/modules/smartxBidAdapter.js @@ -388,7 +388,7 @@ function createOutstreamScript(bid) { insertJSNode('//dco.smartclip.net/?plc=7777778', null, function () { try { - // eslint-disable-next-line no-unused-vars no-undef +// eslint-disable-next-line let _outstreamPlayer = new OutstreamPlayer(divID, smartPlayObj) } catch (e) { utils.logError('[SmartPlay][renderer] Error caught: ' + e); From 9134944af2a35bd9fa8513cf0b7bcdda528dfd56 Mon Sep 17 00:00:00 2001 From: Gino Cirlini Date: Mon, 1 Mar 2021 16:16:19 +0100 Subject: [PATCH 10/29] ESlint fix --- modules/smartxBidAdapter.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/smartxBidAdapter.js b/modules/smartxBidAdapter.js index 3589dc4a573..91a9b730a71 100644 --- a/modules/smartxBidAdapter.js +++ b/modules/smartxBidAdapter.js @@ -387,8 +387,8 @@ function createOutstreamScript(bid) { } insertJSNode('//dco.smartclip.net/?plc=7777778', null, function () { - try { -// eslint-disable-next-line + try { + // eslint-disable-next-line let _outstreamPlayer = new OutstreamPlayer(divID, smartPlayObj) } catch (e) { utils.logError('[SmartPlay][renderer] Error caught: ' + e); From 335b9bdf64cacb59ac63753827e0221a0d5fbfc8 Mon Sep 17 00:00:00 2001 From: Gino Cirlini Date: Fri, 5 Mar 2021 16:13:59 +0100 Subject: [PATCH 11/29] adjust tests, fixes --- modules/smartxBidAdapter.js | 43 ++++++++-------------- modules/smartxBidAdapter.md | 12 +++--- test/spec/modules/smartxBidAdapter_spec.js | 6 ++- 3 files changed, 26 insertions(+), 35 deletions(-) diff --git a/modules/smartxBidAdapter.js b/modules/smartxBidAdapter.js index 91a9b730a71..57225d850b7 100644 --- a/modules/smartxBidAdapter.js +++ b/modules/smartxBidAdapter.js @@ -64,7 +64,7 @@ export const spec = { return false; } if (!utils.getBidIdParameter('outstream_function', bid.params)) { - utils.logMessage(BIDDER_CODE + ': outstream_function parameter is not defined. The default outstream renderer will be injected in the header. You can override the default SmartX outstream rendering by defining your own Outstream function using field outstream_function.'); + utils.logMessage(BIDDER_CODE + ': outstream_function parameter is not defined. The default outstream renderer will be injected in the header.'); return true; } } @@ -311,7 +311,7 @@ export const spec = { return utils.logMessage('SmartX outstream video loaded event'); }, ended: function ended() { - utils.logMessage('SmartX outstream renderer video event'); + return utils.logMessage('SmartX outstream renderer video event'); } }); } catch (err) { @@ -366,34 +366,19 @@ function createOutstreamScript(bid) { smartPlayObj.adResponse = bid.vastContent; - let script = ''; - let divID = '#' + elementId; - - var insertJSNode = function (url, nodeId = null, fun = null) { - script = document.createElement('script'); - script.type = 'text/javascript'; - if (nodeId != null) { - script.id = nodeId; - } - if (url != '') { - script.async = 'true'; - script.src = url; - if (fun != null) { - script.onload = script.onreadystatechange = fun; - } - } else { - script.text = 'var x=' + fun + '();'; - } - } - - insertJSNode('//dco.smartclip.net/?plc=7777778', null, function () { + const divID = '#' + elementId; + var script = document.createElement('script'); + script.src = 'https://dco.smartclip.net/?plc=7777778'; + script.type = 'text/javascript'; + script.async = 'true'; + script.onload = script.onreadystatechange = function () { try { // eslint-disable-next-line - let _outstreamPlayer = new OutstreamPlayer(divID, smartPlayObj) + let _outstreamPlayer = new OutstreamPlayer(divID, smartPlayObj); } catch (e) { utils.logError('[SmartPlay][renderer] Error caught: ' + e); } - }) + }; return script; } @@ -403,8 +388,12 @@ function outstreamRender(bid) { bid.renderer.config.outstream_function(bid, script); } else { try { - const firstScriptTagOnSite = window.document.getElementsByTagName('script')[0]; - firstScriptTagOnSite.parentNode.insertBefore(script, firstScriptTagOnSite); + const slot = utils.getBidIdParameter('slot', bid.renderer.config.outstream_options); + if (slot && window.document.getElementById(slot)) { + window.document.getElementById(slot).appendChild(script); + } else { + window.document.getElementsByTagName('head')[0].appendChild(script); + } } catch (err) { utils.logError('[SMARTX][renderer] Error:' + err.message) } diff --git a/modules/smartxBidAdapter.md b/modules/smartxBidAdapter.md index 082a36f3dde..b25ce68bb6e 100644 --- a/modules/smartxBidAdapter.md +++ b/modules/smartxBidAdapter.md @@ -36,10 +36,10 @@ This adapter requires setup and approval from the smartclip team. slot: 'video1', minAdWidth: 290, maxAdWidth: 900, - headerText: '', + title: '', skipOffset: 0, - startOpen: 'true', - endingScreen: 'true', + startOpen: true, + endingScreen: true, desiredBitrate: 1600, }, } @@ -71,10 +71,10 @@ This adapter requires setup and approval from the smartclip team. slot: 'video1', minAdWidth: 290, maxAdWidth: 900, - headerText: '', + title: '', skipOffset: 0, - startOpen: 'true', - endingScreen: 'true', + startOpen: true, + endingScreen: true, desiredBitrate: 1600, }, user: { diff --git a/test/spec/modules/smartxBidAdapter_spec.js b/test/spec/modules/smartxBidAdapter_spec.js index 82c6642bd74..3bcbbff98d8 100644 --- a/test/spec/modules/smartxBidAdapter_spec.js +++ b/test/spec/modules/smartxBidAdapter_spec.js @@ -421,6 +421,8 @@ describe('The smartx adapter', function () { it('should return an array of bid responses', function () { var responses = spec.interpretResponse(serverResponse, bidderRequestObj); expect(responses).to.be.an('array').with.length(2); + expect(bidderRequestObj).to.be.an('Object'); + expect(bidderRequestObj.bidRequest.bids).to.be.an('array').with.length(2); expect(responses[0].requestId).to.equal(123); expect(responses[0].currency).to.equal('USD'); expect(responses[0].cpm).to.equal(12); @@ -505,8 +507,8 @@ describe('The smartx adapter', function () { responses[0].renderer.render(responses[0]); expect(scriptTag.getAttribute('type')).to.equal('text/javascript'); - expect(scriptTag.getAttribute('src')).to.equal('https://dco.smartclip.net/?plc=7777777'); - + expect(scriptTag.getAttribute('src')).to.equal('https://dco.smartclip.net/?plc=7777778'); + window.document.getElementById.restore(); }); }); From 1ae18d1ec92edbc433e23ab3d7e31773f1e0132a Mon Sep 17 00:00:00 2001 From: Gino Cirlini Date: Fri, 5 Mar 2021 16:29:04 +0100 Subject: [PATCH 12/29] ESlint --- modules/smartxBidAdapter.js | 2 +- test/spec/modules/smartxBidAdapter_spec.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/smartxBidAdapter.js b/modules/smartxBidAdapter.js index 57225d850b7..57c54cb5090 100644 --- a/modules/smartxBidAdapter.js +++ b/modules/smartxBidAdapter.js @@ -390,7 +390,7 @@ function outstreamRender(bid) { try { const slot = utils.getBidIdParameter('slot', bid.renderer.config.outstream_options); if (slot && window.document.getElementById(slot)) { - window.document.getElementById(slot).appendChild(script); + window.document.getElementById(slot).appendChild(script); } else { window.document.getElementsByTagName('head')[0].appendChild(script); } diff --git a/test/spec/modules/smartxBidAdapter_spec.js b/test/spec/modules/smartxBidAdapter_spec.js index 3bcbbff98d8..abc06b48ce7 100644 --- a/test/spec/modules/smartxBidAdapter_spec.js +++ b/test/spec/modules/smartxBidAdapter_spec.js @@ -508,7 +508,7 @@ describe('The smartx adapter', function () { expect(scriptTag.getAttribute('type')).to.equal('text/javascript'); expect(scriptTag.getAttribute('src')).to.equal('https://dco.smartclip.net/?plc=7777778'); - + window.document.getElementById.restore(); }); }); From f6d48166775a1dd4d96c6415c7e8caa324e52520 Mon Sep 17 00:00:00 2001 From: Gino Cirlini Date: Thu, 18 Mar 2021 13:01:28 +0100 Subject: [PATCH 13/29] adjusted desired bitrate examples --- modules/smartxBidAdapter.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/smartxBidAdapter.md b/modules/smartxBidAdapter.md index b25ce68bb6e..223e51763b9 100644 --- a/modules/smartxBidAdapter.md +++ b/modules/smartxBidAdapter.md @@ -40,7 +40,7 @@ This adapter requires setup and approval from the smartclip team. skipOffset: 0, startOpen: true, endingScreen: true, - desiredBitrate: 1600, + desiredBitrate: 800, }, } }], @@ -75,7 +75,7 @@ This adapter requires setup and approval from the smartclip team. skipOffset: 0, startOpen: true, endingScreen: true, - desiredBitrate: 1600, + desiredBitrate: 800, }, user: { data: [{ From 94b7510a9445aede729efb26b6f9b20f2b90dcbf Mon Sep 17 00:00:00 2001 From: Gino Cirlini Date: Thu, 8 Apr 2021 11:41:45 +0200 Subject: [PATCH 14/29] added bid.meta.advertiserDomains support --- modules/smartxBidAdapter.js | 13 +++++++++++++ test/spec/modules/smartxBidAdapter_spec.js | 2 ++ 2 files changed, 15 insertions(+) diff --git a/modules/smartxBidAdapter.js b/modules/smartxBidAdapter.js index 57c54cb5090..b95b31934a2 100644 --- a/modules/smartxBidAdapter.js +++ b/modules/smartxBidAdapter.js @@ -149,6 +149,7 @@ export const spec = { } const language = navigator.language ? 'language' : 'userLanguage'; + const device = { h: screen.height, w: screen.width, @@ -157,8 +158,11 @@ export const spec = { make: navigator.vendor ? navigator.vendor : '', ua: navigator.userAgent }; + const at = utils.getBidIdParameter('at', bid.params) || 2; + const cur = utils.getBidIdParameter('cur', bid.params) || ['EUR']; + const requestPayload = { id: utils.generateUUID(), imp: smartxReq, @@ -176,6 +180,7 @@ export const spec = { at: at, cur: cur }; + const userExt = {}; // Add GDPR flag and consent string @@ -271,6 +276,7 @@ export const spec = { serverResponseBody.cur = pmb.currency; } }); + const bid = { requestId: currentBidRequest.bidId, currency: serverResponseBody.cur || 'USD', @@ -284,7 +290,14 @@ export const spec = { width: smartxBid.w, height: smartxBid.h }; + + bid.meta = bid.meta || {}; + if (smartxBid && smartxBid.adomain && smartxBid.adomain.length > 0) { + bid.meta.advertiserDomains = smartxBid.adomain; + } + const context = utils.deepAccess(currentBidRequest, 'mediaTypes.video.context'); + if (context === 'outstream') { const playersize = utils.deepAccess(currentBidRequest, 'mediaTypes.video.playerSize'); const renderer = Renderer.install({ diff --git a/test/spec/modules/smartxBidAdapter_spec.js b/test/spec/modules/smartxBidAdapter_spec.js index abc06b48ce7..3c871c6f88b 100644 --- a/test/spec/modules/smartxBidAdapter_spec.js +++ b/test/spec/modules/smartxBidAdapter_spec.js @@ -423,6 +423,7 @@ describe('The smartx adapter', function () { expect(responses).to.be.an('array').with.length(2); expect(bidderRequestObj).to.be.an('Object'); expect(bidderRequestObj.bidRequest.bids).to.be.an('array').with.length(2); + expect(responses[0].meta.advertiserDomains[0]).to.equal('abc.com'); expect(responses[0].requestId).to.equal(123); expect(responses[0].currency).to.equal('USD'); expect(responses[0].cpm).to.equal(12); @@ -432,6 +433,7 @@ describe('The smartx adapter', function () { expect(responses[0].mediaType).to.equal('video'); expect(responses[0].width).to.equal(400); expect(responses[0].height).to.equal(300); + expect(responses[1].meta.advertiserDomains[0]).to.equal('def.com'); expect(responses[1].requestId).to.equal(124); expect(responses[1].currency).to.equal('USD'); expect(responses[1].cpm).to.equal(13); From 4861e233a0fd6e99fa2b78fe1768574e1f85a9cc Mon Sep 17 00:00:00 2001 From: Gino Cirlini Date: Wed, 14 Apr 2021 16:08:21 +0200 Subject: [PATCH 15/29] bug fix for numeric elementID outstream render --- modules/smartxBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/smartxBidAdapter.js b/modules/smartxBidAdapter.js index b95b31934a2..6b34e499a99 100644 --- a/modules/smartxBidAdapter.js +++ b/modules/smartxBidAdapter.js @@ -379,7 +379,7 @@ function createOutstreamScript(bid) { smartPlayObj.adResponse = bid.vastContent; - const divID = '#' + elementId; + const divID = '[id="' + elementId + '"]'; var script = document.createElement('script'); script.src = 'https://dco.smartclip.net/?plc=7777778'; script.type = 'text/javascript'; From da45be6e36df51d03d7564c9a2b934b397c78dfa Mon Sep 17 00:00:00 2001 From: Gino Cirlini Date: Fri, 7 May 2021 13:45:30 +0200 Subject: [PATCH 16/29] fix renderer url --- modules/smartxBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/smartxBidAdapter.js b/modules/smartxBidAdapter.js index 6b34e499a99..b2c48b58f82 100644 --- a/modules/smartxBidAdapter.js +++ b/modules/smartxBidAdapter.js @@ -302,7 +302,7 @@ export const spec = { const playersize = utils.deepAccess(currentBidRequest, 'mediaTypes.video.playerSize'); const renderer = Renderer.install({ id: 0, - url: '//', + url: '/', config: { adText: 'SmartX Outstream Video Ad via Prebid.js', player_width: playersize[0][0], From 06edd1e1eb6fa2de11f8147fd2f95acba07d1215 Mon Sep 17 00:00:00 2001 From: Gino Cirlini Date: Tue, 1 Jun 2021 16:15:07 +0200 Subject: [PATCH 17/29] support for floors module --- modules/smartxBidAdapter.js | 42 +++++--- test/spec/modules/smartxBidAdapter_spec.js | 108 +++++++++++++++++++-- 2 files changed, 133 insertions(+), 17 deletions(-) diff --git a/modules/smartxBidAdapter.js b/modules/smartxBidAdapter.js index b2c48b58f82..46e2055c8f0 100644 --- a/modules/smartxBidAdapter.js +++ b/modules/smartxBidAdapter.js @@ -46,14 +46,6 @@ export const spec = { utils.logError(BIDDER_CODE + ': siteId is not present in bidder params'); return false; } - if (!utils.getBidIdParameter('bidfloor', bid.params)) { - utils.logError(BIDDER_CODE + ': bidfloor is not present in bidder params'); - return false; - } - if (!utils.getBidIdParameter('bidfloorcur', bid.params)) { - utils.logError(BIDDER_CODE + ': bidfloorcur is not present in bidder params'); - return false; - } if (utils.deepAccess(bid, 'mediaTypes.video.context') === 'outstream') { if (!utils.getBidIdParameter('outstream_options', bid.params)) { utils.logError(BIDDER_CODE + ': outstream_options parameter is not defined'); @@ -85,8 +77,8 @@ export const spec = { const smartxRequests = bidRequests.map(function (bid) { const tagId = utils.getBidIdParameter('tagId', bid.params); const publisherId = utils.getBidIdParameter('publisherId', bid.params); - const bidfloor = utils.getBidIdParameter('bidfloor', bid.params); - const bidfloorcur = utils.getBidIdParameter('bidfloorcur', bid.params); + const bidfloor = getBidFloor(bid) || 0; + const bidfloorcur = utils.getBidIdParameter('bidfloorcur', bid.params) || 'EUR'; const siteId = utils.getBidIdParameter('siteId', bid.params); const domain = utils.getBidIdParameter('domain', bid.params); const cat = utils.getBidIdParameter('cat', bid.params); @@ -161,7 +153,7 @@ export const spec = { const at = utils.getBidIdParameter('at', bid.params) || 2; - const cur = utils.getBidIdParameter('cur', bid.params) || ['EUR']; + const cur = utils.getBidIdParameter('cur', bid.params) || 'EUR'; const requestPayload = { id: utils.generateUUID(), @@ -226,6 +218,8 @@ export const spec = { } } + // Todo: USER ID MODULE + requestPayload.user = { ext: userExt, data: targetingarr @@ -412,4 +406,30 @@ function outstreamRender(bid) { } } } + +/** + * Get the floor price from bid.params for backward compatibility. + * If not found, then check floor module. + * @param bid A valid bid object + * @returns {*|number} floor price + */ +function getBidFloor(bid) { + let floor = utils.getBidIdParameter('bidfloor', bid.params); + let floorcur = utils.getBidIdParameter('bidfloorcur', bid.params) || 'EUR'; + + if (!floor && utils.isFn(bid.getFloor)) { + const floorObj = bid.getFloor({ + currency: floorcur, + mediaType: '*', + size: '*' + }); + + if (utils.isPlainObject(floorObj) && !isNaN(floorObj.floor) && floorObj.currency === floorcur) { + floor = floorObj.floor; + } + } + + return floor; +} + registerBidder(spec); diff --git a/test/spec/modules/smartxBidAdapter_spec.js b/test/spec/modules/smartxBidAdapter_spec.js index 3c871c6f88b..d0659865afa 100644 --- a/test/spec/modules/smartxBidAdapter_spec.js +++ b/test/spec/modules/smartxBidAdapter_spec.js @@ -108,14 +108,13 @@ describe('The smartx adapter', function () { expect(spec.isBidRequestValid(bid)).to.equal(false); }); - it('should fail without bidfloor', function () { + it('should succeed with floor Module set', function () { delete bid.params.bidfloor; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should fail without bidfloorcur', function () { delete bid.params.bidfloorcur; - expect(spec.isBidRequestValid(bid)).to.equal(false); + bid.floors = { + currency: 'EUR' + }; + expect(spec.isBidRequestValid(bid)).to.equal(true); }); it('should fail with context outstream but no options set for outstream', function () { @@ -514,4 +513,101 @@ describe('The smartx adapter', function () { window.document.getElementById.restore(); }); }); + + describe('price floor module', function () { + var bid, + bidRequestObj; + + beforeEach(function () { + bid = getValidBidObject(); + bidRequestObj = { + refererInfo: { + referer: 'prebid.js' + } + }; + delete bid.params.bidfloor; + }); + + it('obtain floor from getFloor', function () { + bid.getFloor = () => { + return { + currency: 'EUR', + floor: 3.21 + }; + }; + + const payload = spec.buildRequests([bid], bidRequestObj)[0]; + expect(payload.data.imp).to.have.property('bidfloor', 3.21); + }); + + it('obtain floor from params', function() { + bid.getFloor = () => { + return { + currency: 'EUR', + floor: 3.21 + }; + }; + bid.params.bidfloor = 0.64; + + const payload = spec.buildRequests([bid], bidRequestObj)[0]; + expect(payload.data.imp).to.have.property('bidfloor', 0.64); + }); + + it('check currency USD', function() { + bid.getFloor = () => { + return { + currency: 'USD', + floor: 1.23 + }; + }; + bid.params.bidfloorcur = 'USD' + + const payload = spec.buildRequests([bid], bidRequestObj)[0]; + expect(payload.data.imp).to.have.property('bidfloorcur', 'USD'); + expect(payload.data.imp).to.have.property('bidfloor', 1.23); + }); + + it('check defaut currency EUR', function() { + delete bid.params.bidfloorcur; + + bid.getFloor = () => { + return { + currency: 'EUR', + floor: 4.56 + }; + }; + + const payload = spec.buildRequests([bid], bidRequestObj)[0]; + expect(payload.data.imp).to.have.property('bidfloorcur', 'EUR'); + expect(payload.data.imp).to.have.property('bidfloor', 4.56); + }); + + it('bad floor value', function() { + bid.getFloor = () => { + return { + currency: 'EUR', + floor: 'bad' + }; + }; + + const payload = spec.buildRequests([bid], bidRequestObj)[0]; + expect(payload.data.imp).to.have.property('bidfloor', 0); + }); + + it('empty floor object', function() { + bid.getFloor = () => { + return {}; + }; + + const payload = spec.buildRequests([bid], bidRequestObj)[0]; + expect(payload.data.imp).to.have.property('bidfloor', 0); + }); + + it('undefined floor result', function() { + bid.getFloor = () => {}; + + const payload = spec.buildRequests([bid], bidRequestObj)[0]; + expect(payload.data.imp).to.have.property('bidfloor', 0); + }); + }); }) From d1bd22aa226094be029ab046192578c1433ca460 Mon Sep 17 00:00:00 2001 From: Gino Cirlini Date: Thu, 10 Jun 2021 16:26:49 +0200 Subject: [PATCH 18/29] bugfixes to be openRTB 2.5 compliant --- modules/smartxBidAdapter.js | 9 ++-- test/spec/modules/smartxBidAdapter_spec.js | 55 +++++++++++----------- 2 files changed, 31 insertions(+), 33 deletions(-) diff --git a/modules/smartxBidAdapter.js b/modules/smartxBidAdapter.js index 46e2055c8f0..657520bcde5 100644 --- a/modules/smartxBidAdapter.js +++ b/modules/smartxBidAdapter.js @@ -81,7 +81,7 @@ export const spec = { const bidfloorcur = utils.getBidIdParameter('bidfloorcur', bid.params) || 'EUR'; const siteId = utils.getBidIdParameter('siteId', bid.params); const domain = utils.getBidIdParameter('domain', bid.params); - const cat = utils.getBidIdParameter('cat', bid.params); + const cat = utils.getBidIdParameter('cat', bid.params) || ['']; let pubcid = null; const playerSize = utils.deepAccess(bid, 'mediaTypes.video.playerSize'); const contentWidth = playerSize[0][0]; @@ -108,7 +108,7 @@ export const spec = { placement = 3; } - let smartxReq = { + let smartxReq = [{ id: bid.bidId, secure: secure, bidfloor: bidfloor, @@ -134,7 +134,7 @@ export const spec = { ext: { 'smart.bidpricetype': 1 } - }; + }]; if (bid.crumbs && bid.crumbs.pubcid) { pubcid = bid.crumbs.pubcid; @@ -153,7 +153,7 @@ export const spec = { const at = utils.getBidIdParameter('at', bid.params) || 2; - const cur = utils.getBidIdParameter('cur', bid.params) || 'EUR'; + const cur = utils.getBidIdParameter('cur', bid.params) || ['EUR']; const requestPayload = { id: utils.generateUUID(), @@ -162,7 +162,6 @@ export const spec = { id: siteId, page: page, cat: cat, - content: 'content', domain: domain, publisher: { id: publisherId diff --git a/test/spec/modules/smartxBidAdapter_spec.js b/test/spec/modules/smartxBidAdapter_spec.js index d0659865afa..f02aa81980f 100644 --- a/test/spec/modules/smartxBidAdapter_spec.js +++ b/test/spec/modules/smartxBidAdapter_spec.js @@ -155,10 +155,10 @@ describe('The smartx adapter', function () { expect(request.method).to.equal('POST'); expect(request.url).to.equal('https://bid.sxp.smartclip.net/bid/1000'); expect(request.bidRequest).to.equal(bidRequestObj); - expect(request.data.imp.id).to.match(/\d+/); - expect(request.data.imp.secure).to.equal(0); + expect(request.data.imp[0].id).to.match(/\d+/); + expect(request.data.imp[0].secure).to.equal(0); - expect(request.data.imp.video).to.deep.equal({ + expect(request.data.imp[0].video).to.deep.equal({ ext: { sdk_name: 'Prebid 1+' }, @@ -183,10 +183,9 @@ describe('The smartx adapter', function () { }); expect(request.data.site).to.deep.equal({ - content: 'content', id: '__name__', page: 'prebid.js', - cat: '', + cat: [''], domain: '', publisher: { id: '__name__' @@ -197,11 +196,11 @@ describe('The smartx adapter', function () { it('should change request parameters based on options sent', function () { var request = spec.buildRequests([bid], bidRequestObj)[0]; - expect(request.data.imp.video.ext).to.deep.equal({ + expect(request.data.imp[0].video.ext).to.deep.equal({ sdk_name: 'Prebid 1+' }); - expect(request.data.imp.video).to.contain({ + expect(request.data.imp[0].video).to.contain({ placement: 1 }); @@ -233,30 +232,30 @@ describe('The smartx adapter', function () { request = spec.buildRequests([bid], bidRequestObj)[0]; - expect(request.data.imp.video.ext).to.deep.equal({ + expect(request.data.imp[0].video.ext).to.deep.equal({ sdk_name: 'Prebid 1+' }); - expect(request.data.imp.video).to.contain({ + expect(request.data.imp[0].video).to.contain({ minduration: 5, maxduration: 10 }); - expect(request.data.imp.video.startdelay).to.equal(1); + expect(request.data.imp[0].video.startdelay).to.equal(1); - expect(request.data.imp.video).to.contain({ + expect(request.data.imp[0].video).to.contain({ placement: 3 }); - expect(request.data.imp.bidfloor).to.equal(55); + expect(request.data.imp[0].bidfloor).to.equal(55); - expect(request.data.imp.bidfloorcur).to.equal('foo'); + expect(request.data.imp[0].bidfloorcur).to.equal('foo'); - expect(request.data.imp.video.linearity).to.equal(2); + expect(request.data.imp[0].video.linearity).to.equal(2); - expect(request.data.imp.video.minbitrate).to.equal(50); + expect(request.data.imp[0].video.minbitrate).to.equal(50); - expect(request.data.imp.video.maxbitrate).to.equal(500); + expect(request.data.imp[0].video.maxbitrate).to.equal(500); }); it('should pass GDPR params', function () { @@ -321,7 +320,7 @@ describe('The smartx adapter', function () { request = spec.buildRequests([bid], bidRequestObj)[0]; - expect(request.data.imp.video.linearity).to.equal(3); + expect(request.data.imp[0].video.linearity).to.equal(3); }); it('should pass min and max duration params', function () { @@ -332,8 +331,8 @@ describe('The smartx adapter', function () { request = spec.buildRequests([bid], bidRequestObj)[0]; - expect(request.data.imp.video.minduration).to.equal(3); - expect(request.data.imp.video.maxduration).to.equal(15); + expect(request.data.imp[0].video.minduration).to.equal(3); + expect(request.data.imp[0].video.maxduration).to.equal(15); }); }); @@ -537,7 +536,7 @@ describe('The smartx adapter', function () { }; const payload = spec.buildRequests([bid], bidRequestObj)[0]; - expect(payload.data.imp).to.have.property('bidfloor', 3.21); + expect(payload.data.imp[0]).to.have.property('bidfloor', 3.21); }); it('obtain floor from params', function() { @@ -550,7 +549,7 @@ describe('The smartx adapter', function () { bid.params.bidfloor = 0.64; const payload = spec.buildRequests([bid], bidRequestObj)[0]; - expect(payload.data.imp).to.have.property('bidfloor', 0.64); + expect(payload.data.imp[0]).to.have.property('bidfloor', 0.64); }); it('check currency USD', function() { @@ -563,8 +562,8 @@ describe('The smartx adapter', function () { bid.params.bidfloorcur = 'USD' const payload = spec.buildRequests([bid], bidRequestObj)[0]; - expect(payload.data.imp).to.have.property('bidfloorcur', 'USD'); - expect(payload.data.imp).to.have.property('bidfloor', 1.23); + expect(payload.data.imp[0]).to.have.property('bidfloorcur', 'USD'); + expect(payload.data.imp[0]).to.have.property('bidfloor', 1.23); }); it('check defaut currency EUR', function() { @@ -578,8 +577,8 @@ describe('The smartx adapter', function () { }; const payload = spec.buildRequests([bid], bidRequestObj)[0]; - expect(payload.data.imp).to.have.property('bidfloorcur', 'EUR'); - expect(payload.data.imp).to.have.property('bidfloor', 4.56); + expect(payload.data.imp[0]).to.have.property('bidfloorcur', 'EUR'); + expect(payload.data.imp[0]).to.have.property('bidfloor', 4.56); }); it('bad floor value', function() { @@ -591,7 +590,7 @@ describe('The smartx adapter', function () { }; const payload = spec.buildRequests([bid], bidRequestObj)[0]; - expect(payload.data.imp).to.have.property('bidfloor', 0); + expect(payload.data.imp[0]).to.have.property('bidfloor', 0); }); it('empty floor object', function() { @@ -600,14 +599,14 @@ describe('The smartx adapter', function () { }; const payload = spec.buildRequests([bid], bidRequestObj)[0]; - expect(payload.data.imp).to.have.property('bidfloor', 0); + expect(payload.data.imp[0]).to.have.property('bidfloor', 0); }); it('undefined floor result', function() { bid.getFloor = () => {}; const payload = spec.buildRequests([bid], bidRequestObj)[0]; - expect(payload.data.imp).to.have.property('bidfloor', 0); + expect(payload.data.imp[0]).to.have.property('bidfloor', 0); }); }); }) From def250bf25aebc92bc61b688dbdeaf65a1ef5449 Mon Sep 17 00:00:00 2001 From: Gino Cirlini Date: Thu, 1 Jul 2021 17:30:11 +0200 Subject: [PATCH 19/29] update internal renderer usage --- modules/smartxBidAdapter.js | 41 +++++----------------- test/spec/modules/smartxBidAdapter_spec.js | 5 +-- 2 files changed, 12 insertions(+), 34 deletions(-) diff --git a/modules/smartxBidAdapter.js b/modules/smartxBidAdapter.js index 657520bcde5..bed1d680246 100644 --- a/modules/smartxBidAdapter.js +++ b/modules/smartxBidAdapter.js @@ -295,7 +295,7 @@ export const spec = { const playersize = utils.deepAccess(currentBidRequest, 'mediaTypes.video.playerSize'); const renderer = Renderer.install({ id: 0, - url: '/', + url: 'https://dco.smartclip.net/?plc=7777778', config: { adText: 'SmartX Outstream Video Ad via Prebid.js', player_width: playersize[0][0], @@ -308,7 +308,7 @@ export const spec = { } }); try { - renderer.setRender(outstreamRender); + renderer.setRender(createOutstreamConfig); renderer.setEventHandlers({ impression: function impression() { return utils.logMessage('SmartX outstream video impression event'); @@ -333,7 +333,7 @@ export const spec = { } } -function createOutstreamScript(bid) { +function createOutstreamConfig(bid) { const confMinAdWidth = utils.getBidIdParameter('minAdWidth', bid.renderer.config.outstream_options) || 290; const confMaxAdWidth = utils.getBidIdParameter('maxAdWidth', bid.renderer.config.outstream_options) || 900; const confStartOpen = utils.getBidIdParameter('startOpen', bid.renderer.config.outstream_options); @@ -373,37 +373,14 @@ function createOutstreamScript(bid) { smartPlayObj.adResponse = bid.vastContent; const divID = '[id="' + elementId + '"]'; - var script = document.createElement('script'); - script.src = 'https://dco.smartclip.net/?plc=7777778'; - script.type = 'text/javascript'; - script.async = 'true'; - script.onload = script.onreadystatechange = function () { - try { - // eslint-disable-next-line - let _outstreamPlayer = new OutstreamPlayer(divID, smartPlayObj); - } catch (e) { - utils.logError('[SmartPlay][renderer] Error caught: ' + e); - } - }; - return script; -} -function outstreamRender(bid) { - const script = createOutstreamScript(bid); - if (bid.renderer.config.outstream_function != null && typeof bid.renderer.config.outstream_function === 'function') { - bid.renderer.config.outstream_function(bid, script); - } else { - try { - const slot = utils.getBidIdParameter('slot', bid.renderer.config.outstream_options); - if (slot && window.document.getElementById(slot)) { - window.document.getElementById(slot).appendChild(script); - } else { - window.document.getElementsByTagName('head')[0].appendChild(script); - } - } catch (err) { - utils.logError('[SMARTX][renderer] Error:' + err.message) - } + try { + // eslint-disable-next-line + let _outstreamPlayer = new OutstreamPlayer(divID, smartPlayObj); + } catch (e) { + utils.logError('[SMARTX][renderer] Error caught: ' + e); } + return smartPlayObj; } /** diff --git a/test/spec/modules/smartxBidAdapter_spec.js b/test/spec/modules/smartxBidAdapter_spec.js index f02aa81980f..89c03034ba4 100644 --- a/test/spec/modules/smartxBidAdapter_spec.js +++ b/test/spec/modules/smartxBidAdapter_spec.js @@ -506,8 +506,9 @@ describe('The smartx adapter', function () { responses[0].renderer.render(responses[0]); - expect(scriptTag.getAttribute('type')).to.equal('text/javascript'); - expect(scriptTag.getAttribute('src')).to.equal('https://dco.smartclip.net/?plc=7777778'); + // expect(scriptTag.getAttribute('type')).to.equal('text/javascript'); + // expect(scriptTag.getAttribute('src')).to.equal('https://dco.smartclip.net/?plc=7777778'); + expect(responses[0].renderer.url).to.equal('https://dco.smartclip.net/?plc=7777778'); window.document.getElementById.restore(); }); From cec1e823da2afb6277b5ffcc800af49dfbf8d7e4 Mon Sep 17 00:00:00 2001 From: Gino Cirlini Date: Thu, 1 Jul 2021 17:36:33 +0200 Subject: [PATCH 20/29] remove unused outstream_function logic --- modules/smartxBidAdapter.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/modules/smartxBidAdapter.js b/modules/smartxBidAdapter.js index bed1d680246..c29708c8420 100644 --- a/modules/smartxBidAdapter.js +++ b/modules/smartxBidAdapter.js @@ -55,10 +55,6 @@ export const spec = { utils.logError(BIDDER_CODE + ': slot parameter is not defined in outstream_options object in the configuration'); return false; } - if (!utils.getBidIdParameter('outstream_function', bid.params)) { - utils.logMessage(BIDDER_CODE + ': outstream_function parameter is not defined. The default outstream renderer will be injected in the header.'); - return true; - } } return true; @@ -303,8 +299,7 @@ export const spec = { content_page_url: utils.deepAccess(bidderRequest, 'data.site.page'), ad_mute: +!!utils.deepAccess(currentBidRequest, 'params.ad_mute'), hide_skin: +!!utils.deepAccess(currentBidRequest, 'params.hide_skin'), - outstream_options: utils.deepAccess(currentBidRequest, 'params.outstream_options'), - outstream_function: utils.deepAccess(currentBidRequest, 'params.outstream_function') + outstream_options: utils.deepAccess(currentBidRequest, 'params.outstream_options') } }); try { From 48b6f44e6c78216d9c90e3b2e71dc895c43649f9 Mon Sep 17 00:00:00 2001 From: Gino Cirlini Date: Wed, 1 Sep 2021 15:09:26 +0200 Subject: [PATCH 21/29] bugfix outstream options for default outstream renderer configuration --- modules/smartxBidAdapter.js | 47 ++++++++++++----- modules/smartxBidAdapter.md | 8 +-- test/spec/modules/smartxBidAdapter_spec.js | 61 ++++++++++++++++++---- 3 files changed, 89 insertions(+), 27 deletions(-) diff --git a/modules/smartxBidAdapter.js b/modules/smartxBidAdapter.js index c29708c8420..44e70082d9d 100644 --- a/modules/smartxBidAdapter.js +++ b/modules/smartxBidAdapter.js @@ -329,25 +329,20 @@ export const spec = { } function createOutstreamConfig(bid) { - const confMinAdWidth = utils.getBidIdParameter('minAdWidth', bid.renderer.config.outstream_options) || 290; - const confMaxAdWidth = utils.getBidIdParameter('maxAdWidth', bid.renderer.config.outstream_options) || 900; - const confStartOpen = utils.getBidIdParameter('startOpen', bid.renderer.config.outstream_options); - const confEndingScreen = utils.getBidIdParameter('endingScreen', bid.renderer.config.outstream_options); - const confTitle = utils.getBidIdParameter('title', bid.renderer.config.outstream_options); - const confSkipOffset = utils.getBidIdParameter('skipOffset', bid.renderer.config.outstream_options); - const confDesiredBitrate = utils.getBidIdParameter('desiredBitrate', bid.renderer.config.outstream_options); - const elementId = utils.getBidIdParameter('slot', bid.renderer.config.outstream_options) || bid.adUnitCode; + let confMinAdWidth = utils.getBidIdParameter('minAdWidth', bid.renderer.config.outstream_options) || 290; + let confMaxAdWidth = utils.getBidIdParameter('maxAdWidth', bid.renderer.config.outstream_options) || 900; + let confStartOpen = utils.getBidIdParameter('startOpen', bid.renderer.config.outstream_options) + let confEndingScreen = utils.getBidIdParameter('endingScreen', bid.renderer.config.outstream_options) + let confTitle = utils.getBidIdParameter('title', bid.renderer.config.outstream_options); + let confSkipOffset = utils.getBidIdParameter('skipOffset', bid.renderer.config.outstream_options); + let confDesiredBitrate = utils.getBidIdParameter('desiredBitrate', bid.renderer.config.outstream_options); + let elementId = utils.getBidIdParameter('slot', bid.renderer.config.outstream_options) || bid.adUnitCode; utils.logMessage('[SMARTX][renderer] Handle SmartX outstream renderer'); var smartPlayObj = { minAdWidth: confMinAdWidth, maxAdWidth: confMaxAdWidth, - title: confTitle, - skipOffset: confSkipOffset, - startOpen: confStartOpen, - endingScreen: confEndingScreen, - desiredBitrate: confDesiredBitrate, onStartCallback: function (m, n) { try { window.sc_smartIntxtStart(n); @@ -365,13 +360,37 @@ function createOutstreamConfig(bid) { }, }; + if (confStartOpen == 'true') { + smartPlayObj.startOpen = true; + } else if (confStartOpen == 'false') { + smartPlayObj.startOpen = false; + } + + if (confEndingScreen == 'true') { + smartPlayObj.endingScreen = true; + } else if (confEndingScreen == 'false') { + smartPlayObj.endingScreen = false; + } + + if (confTitle) { + smartPlayObj.title = confTitle; + } + + if (confSkipOffset) { + smartPlayObj.skipOffset = confSkipOffset; + } + + if (confDesiredBitrate) { + smartPlayObj.desiredBitrate = confDesiredBitrate; + } + smartPlayObj.adResponse = bid.vastContent; const divID = '[id="' + elementId + '"]'; try { // eslint-disable-next-line - let _outstreamPlayer = new OutstreamPlayer(divID, smartPlayObj); + let _outstreamPlayer = new OutstreamPlayer(divID, smartPlayObj); } catch (e) { utils.logError('[SMARTX][renderer] Error caught: ' + e); } diff --git a/modules/smartxBidAdapter.md b/modules/smartxBidAdapter.md index 223e51763b9..853f06d6baf 100644 --- a/modules/smartxBidAdapter.md +++ b/modules/smartxBidAdapter.md @@ -38,8 +38,8 @@ This adapter requires setup and approval from the smartclip team. maxAdWidth: 900, title: '', skipOffset: 0, - startOpen: true, - endingScreen: true, + startOpen: 'true', + endingScreen: 'true', desiredBitrate: 800, }, } @@ -73,8 +73,8 @@ This adapter requires setup and approval from the smartclip team. maxAdWidth: 900, title: '', skipOffset: 0, - startOpen: true, - endingScreen: true, + startOpen: 'true', + endingScreen: 'true', desiredBitrate: 800, }, user: { diff --git a/test/spec/modules/smartxBidAdapter_spec.js b/test/spec/modules/smartxBidAdapter_spec.js index 89c03034ba4..4e560c87df3 100644 --- a/test/spec/modules/smartxBidAdapter_spec.js +++ b/test/spec/modules/smartxBidAdapter_spec.js @@ -495,7 +495,7 @@ describe('The smartx adapter', function () { }; }); - it('should attempt to insert the script', function () { + it('should attempt to insert the script without outstream config options set', function () { var scriptTag; sinon.stub(window.document, 'getElementById').returns({ appendChild: sinon.stub().callsFake(function (script) { @@ -506,8 +506,51 @@ describe('The smartx adapter', function () { responses[0].renderer.render(responses[0]); - // expect(scriptTag.getAttribute('type')).to.equal('text/javascript'); - // expect(scriptTag.getAttribute('src')).to.equal('https://dco.smartclip.net/?plc=7777778'); + expect(responses[0].renderer.url).to.equal('https://dco.smartclip.net/?plc=7777778'); + + window.document.getElementById.restore(); + }); + + it('should attempt to insert the script with outstream config options set', function () { + var scriptTag; + sinon.stub(window.document, 'getElementById').returns({ + appendChild: sinon.stub().callsFake(function (script) { + scriptTag = script + }) + }); + var responses = spec.interpretResponse(serverResponse, bidderRequestObj); + + bidderRequestObj.bidRequest.bids[0].params.outstream_options.startOpen = 'true'; + bidderRequestObj.bidRequest.bids[0].params.outstream_options.endingScreen = 'true'; + bidderRequestObj.bidRequest.bids[0].params.outstream_options.title = 'abc'; + bidderRequestObj.bidRequest.bids[0].params.outstream_options.skipOffset = 2; + bidderRequestObj.bidRequest.bids[0].params.outstream_options.desiredBitrate = 123; + + responses[0].renderer.render(responses[0]); + + bidderRequestObj.bidRequest.bids[0].params.outstream_options.startOpen = 'false'; + bidderRequestObj.bidRequest.bids[0].params.outstream_options.endingScreen = 'false'; + + responses[0].renderer.render(responses[0]); + + expect(responses[0].renderer.url).to.equal('https://dco.smartclip.net/?plc=7777778'); + + window.document.getElementById.restore(); + }); + + it('should attempt to insert the script without defined slot', function () { + var scriptTag; + sinon.stub(window.document, 'getElementById').returns({ + appendChild: sinon.stub().callsFake(function (script) { + scriptTag = script + }) + }); + var responses = spec.interpretResponse(serverResponse, bidderRequestObj); + + delete bidderRequestObj.bidRequest.bids[0].params.outstream_options.slot; + + responses[0].renderer.render(responses[0]); + expect(responses[0].renderer.url).to.equal('https://dco.smartclip.net/?plc=7777778'); window.document.getElementById.restore(); @@ -540,7 +583,7 @@ describe('The smartx adapter', function () { expect(payload.data.imp[0]).to.have.property('bidfloor', 3.21); }); - it('obtain floor from params', function() { + it('obtain floor from params', function () { bid.getFloor = () => { return { currency: 'EUR', @@ -553,7 +596,7 @@ describe('The smartx adapter', function () { expect(payload.data.imp[0]).to.have.property('bidfloor', 0.64); }); - it('check currency USD', function() { + it('check currency USD', function () { bid.getFloor = () => { return { currency: 'USD', @@ -567,7 +610,7 @@ describe('The smartx adapter', function () { expect(payload.data.imp[0]).to.have.property('bidfloor', 1.23); }); - it('check defaut currency EUR', function() { + it('check defaut currency EUR', function () { delete bid.params.bidfloorcur; bid.getFloor = () => { @@ -582,7 +625,7 @@ describe('The smartx adapter', function () { expect(payload.data.imp[0]).to.have.property('bidfloor', 4.56); }); - it('bad floor value', function() { + it('bad floor value', function () { bid.getFloor = () => { return { currency: 'EUR', @@ -594,7 +637,7 @@ describe('The smartx adapter', function () { expect(payload.data.imp[0]).to.have.property('bidfloor', 0); }); - it('empty floor object', function() { + it('empty floor object', function () { bid.getFloor = () => { return {}; }; @@ -603,7 +646,7 @@ describe('The smartx adapter', function () { expect(payload.data.imp[0]).to.have.property('bidfloor', 0); }); - it('undefined floor result', function() { + it('undefined floor result', function () { bid.getFloor = () => {}; const payload = spec.buildRequests([bid], bidRequestObj)[0]; From 354383519276a8033b925b5e5723b4a662c3482e Mon Sep 17 00:00:00 2001 From: Gino Cirlini Date: Wed, 15 Sep 2021 11:00:56 +0200 Subject: [PATCH 22/29] [PREB-10] fix empty title not configurable --- modules/smartxBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/smartxBidAdapter.js b/modules/smartxBidAdapter.js index 44e70082d9d..73e036cadb0 100644 --- a/modules/smartxBidAdapter.js +++ b/modules/smartxBidAdapter.js @@ -372,7 +372,7 @@ function createOutstreamConfig(bid) { smartPlayObj.endingScreen = false; } - if (confTitle) { + if (confTitle || (typeof bid.renderer.config.outstream_options.title == 'string' && bid.renderer.config.outstream_options.title == '')) { smartPlayObj.title = confTitle; } From cf854200774c0eba66053207ae72d2fd280075fe Mon Sep 17 00:00:00 2001 From: Gino Cirlini Date: Thu, 7 Oct 2021 10:34:24 +0200 Subject: [PATCH 23/29] add pbjs version --- modules/smartxBidAdapter.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/modules/smartxBidAdapter.js b/modules/smartxBidAdapter.js index 73e036cadb0..b416cf61144 100644 --- a/modules/smartxBidAdapter.js +++ b/modules/smartxBidAdapter.js @@ -165,7 +165,15 @@ export const spec = { }, device: device, at: at, - cur: cur + cur: cur, + ext: { + prebid: { + channel: { + name: 'pbjs', + version: '$prebid.version$' + } + } + } }; const userExt = {}; @@ -194,6 +202,8 @@ export const spec = { }; } + // requestPayload.user.ext.ver = pbjs.version; + // Targeting if (utils.getBidIdParameter('data', bid.params.user)) { var targetingarr = []; From 440735c6f1c0a55db2e0f9dbad743f25e7b45006 Mon Sep 17 00:00:00 2001 From: Gino Cirlini Date: Tue, 12 Oct 2021 14:02:09 +0200 Subject: [PATCH 24/29] testing with outstream 5.3.0 --- modules/smartxBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/smartxBidAdapter.js b/modules/smartxBidAdapter.js index b416cf61144..929e2efeb76 100644 --- a/modules/smartxBidAdapter.js +++ b/modules/smartxBidAdapter.js @@ -301,7 +301,7 @@ export const spec = { const playersize = utils.deepAccess(currentBidRequest, 'mediaTypes.video.playerSize'); const renderer = Renderer.install({ id: 0, - url: 'https://dco.smartclip.net/?plc=7777778', + url: 'https://dco.smartclip.net/?plc=123456789', // Outstream 5.3.0 config: { adText: 'SmartX Outstream Video Ad via Prebid.js', player_width: playersize[0][0], From 8a77a00d5bf1d29bf39f349fb591ec1aec031f6d Mon Sep 17 00:00:00 2001 From: Gino Cirlini Date: Tue, 9 Nov 2021 15:58:40 +0100 Subject: [PATCH 25/29] pbjs version into content.ext --- modules/smartxBidAdapter.js | 23 ++++++++++++---------- test/spec/modules/smartxBidAdapter_spec.js | 10 ++++++++++ 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/modules/smartxBidAdapter.js b/modules/smartxBidAdapter.js index f7d30d87015..598fe0024ec 100644 --- a/modules/smartxBidAdapter.js +++ b/modules/smartxBidAdapter.js @@ -161,19 +161,22 @@ export const spec = { domain: domain, publisher: { id: publisherId + }, + content: { + ext: { + prebid: { + channel: { + name: 'pbjs', + version: '$prebid.version$' + } + } + } } }, device: device, at: at, cur: cur, - ext: { - prebid: { - channel: { - name: 'pbjs', - version: '$prebid.version$' - } - } - } + ext: {} }; const userExt = {}; @@ -202,7 +205,7 @@ export const spec = { }; } - // requestPayload.user.ext.ver = pbjs.version; + // requestPayload.user.ext.ver = pbjs.version; // Targeting if (getBidIdParameter('data', bid.params.user)) { @@ -301,7 +304,7 @@ export const spec = { const playersize = deepAccess(currentBidRequest, 'mediaTypes.video.playerSize'); const renderer = Renderer.install({ id: 0, - url: 'https://dco.smartclip.net/?plc=123456789', // Outstream 5.3.0 + url: 'https://dco.smartclip.net/?plc=7777778', config: { adText: 'SmartX Outstream Video Ad via Prebid.js', player_width: playersize[0][0], diff --git a/test/spec/modules/smartxBidAdapter_spec.js b/test/spec/modules/smartxBidAdapter_spec.js index 4e560c87df3..ebb430a18c1 100644 --- a/test/spec/modules/smartxBidAdapter_spec.js +++ b/test/spec/modules/smartxBidAdapter_spec.js @@ -189,6 +189,16 @@ describe('The smartx adapter', function () { domain: '', publisher: { id: '__name__' + }, + content: { + ext: { + prebid: { + channel: { + name: 'pbjs', + version: '$prebid.version$' + } + } + } } }); }); From d40390453ad2e391a6dcc5cc852529098dfdf6ec Mon Sep 17 00:00:00 2001 From: Gino Cirlini Date: Tue, 9 Nov 2021 17:11:16 +0100 Subject: [PATCH 26/29] made visibilityThreshold configurable --- modules/smartxBidAdapter.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/smartxBidAdapter.js b/modules/smartxBidAdapter.js index 598fe0024ec..1e19184a423 100644 --- a/modules/smartxBidAdapter.js +++ b/modules/smartxBidAdapter.js @@ -349,6 +349,7 @@ function createOutstreamConfig(bid) { let confTitle = getBidIdParameter('title', bid.renderer.config.outstream_options); let confSkipOffset = getBidIdParameter('skipOffset', bid.renderer.config.outstream_options); let confDesiredBitrate = getBidIdParameter('desiredBitrate', bid.renderer.config.outstream_options); + let confVisibilityThreshold = getBidIdParameter('visibilityThreshold', bid.renderer.config.outstream_options); let elementId = getBidIdParameter('slot', bid.renderer.config.outstream_options) || bid.adUnitCode; logMessage('[SMARTX][renderer] Handle SmartX outstream renderer'); @@ -397,6 +398,10 @@ function createOutstreamConfig(bid) { smartPlayObj.desiredBitrate = confDesiredBitrate; } + if (confVisibilityThreshold) { + smartPlayObj.visibilityThreshold = confVisibilityThreshold; + } + smartPlayObj.adResponse = bid.vastContent; const divID = '[id="' + elementId + '"]'; From 59f652110ec3be21d3a48064da6002371eea7752 Mon Sep 17 00:00:00 2001 From: Gino Cirlini Date: Thu, 11 Nov 2021 14:32:58 +0100 Subject: [PATCH 27/29] adjust position of pbjs version --- modules/smartxBidAdapter.js | 6 ++---- test/spec/modules/smartxBidAdapter_spec.js | 7 +++---- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/modules/smartxBidAdapter.js b/modules/smartxBidAdapter.js index 1e19184a423..00c962445d9 100644 --- a/modules/smartxBidAdapter.js +++ b/modules/smartxBidAdapter.js @@ -165,10 +165,8 @@ export const spec = { content: { ext: { prebid: { - channel: { - name: 'pbjs', - version: '$prebid.version$' - } + name: 'pbjs', + version: '$prebid.version$' } } } diff --git a/test/spec/modules/smartxBidAdapter_spec.js b/test/spec/modules/smartxBidAdapter_spec.js index ebb430a18c1..ddee2fa3347 100644 --- a/test/spec/modules/smartxBidAdapter_spec.js +++ b/test/spec/modules/smartxBidAdapter_spec.js @@ -193,10 +193,8 @@ describe('The smartx adapter', function () { content: { ext: { prebid: { - channel: { - name: 'pbjs', - version: '$prebid.version$' - } + name: 'pbjs', + version: '$prebid.version$' } } } @@ -535,6 +533,7 @@ describe('The smartx adapter', function () { bidderRequestObj.bidRequest.bids[0].params.outstream_options.title = 'abc'; bidderRequestObj.bidRequest.bids[0].params.outstream_options.skipOffset = 2; bidderRequestObj.bidRequest.bids[0].params.outstream_options.desiredBitrate = 123; + bidderRequestObj.bidRequest.bids[0].params.outstream_options.visibilityThreshold = 30; responses[0].renderer.render(responses[0]); From d7b5826bde9b85d3ac649f634c801f83c1d0e8e5 Mon Sep 17 00:00:00 2001 From: Gino Cirlini Date: Wed, 20 Jul 2022 15:18:19 +0200 Subject: [PATCH 28/29] Merge branch 'master' of https://github.com/prebid/Prebid.js into HEAD --- .../gpt/1plusXRtdProviderExample.html | 112 + modules/1plusXRtdProvider.js | 251 +++ modules/1plusXRtdProvider.md | 67 + modules/kargoAnalyticsAdapter.md | 33 + modules/newspassidBidAdapter.js | 649 ++++++ modules/openxOrtbBidAdapter.js | 349 ++++ modules/openxOrtbBidAdapter.md | 105 + modules/rasBidAdapter.js | 149 ++ modules/rasBidAdapter.md | 50 + modules/shinezBidAdapter.js | 435 ++++ test/spec/modules/1plusXRtdProvider_spec.js | 430 ++++ .../modules/kargoAnalyticsAdapter_spec.js | 42 + .../spec/modules/newspassidBidAdapter_spec.js | 1793 +++++++++++++++++ test/spec/modules/openxOrtbBidAdapter_spec.js | 1303 ++++++++++++ test/spec/modules/rasBidAdapter_spec.js | 190 ++ test/spec/modules/shinezBidAdapter_spec.js | 479 +++++ 16 files changed, 6437 insertions(+) create mode 100644 integrationExamples/gpt/1plusXRtdProviderExample.html create mode 100644 modules/1plusXRtdProvider.js create mode 100644 modules/1plusXRtdProvider.md create mode 100644 modules/kargoAnalyticsAdapter.md create mode 100644 modules/newspassidBidAdapter.js create mode 100644 modules/openxOrtbBidAdapter.js create mode 100644 modules/openxOrtbBidAdapter.md create mode 100644 modules/rasBidAdapter.js create mode 100644 modules/rasBidAdapter.md create mode 100644 modules/shinezBidAdapter.js create mode 100644 test/spec/modules/1plusXRtdProvider_spec.js create mode 100644 test/spec/modules/kargoAnalyticsAdapter_spec.js create mode 100644 test/spec/modules/newspassidBidAdapter_spec.js create mode 100644 test/spec/modules/openxOrtbBidAdapter_spec.js create mode 100644 test/spec/modules/rasBidAdapter_spec.js create mode 100644 test/spec/modules/shinezBidAdapter_spec.js diff --git a/integrationExamples/gpt/1plusXRtdProviderExample.html b/integrationExamples/gpt/1plusXRtdProviderExample.html new file mode 100644 index 00000000000..2eb75063df1 --- /dev/null +++ b/integrationExamples/gpt/1plusXRtdProviderExample.html @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + +

1plusX RTD Module for Prebid

+ +
+ +
+ + + \ No newline at end of file diff --git a/modules/1plusXRtdProvider.js b/modules/1plusXRtdProvider.js new file mode 100644 index 00000000000..5affafcf9d3 --- /dev/null +++ b/modules/1plusXRtdProvider.js @@ -0,0 +1,251 @@ +import { submodule } from '../src/hook.js'; +import { config } from '../src/config.js'; +import { ajax } from '../src/ajax.js'; +import { + logMessage, logError, + deepAccess, mergeDeep, + isNumber, isArray, deepSetValue +} from '../src/utils.js'; + +// Constants +const REAL_TIME_MODULE = 'realTimeData'; +const MODULE_NAME = '1plusX'; +const ORTB2_NAME = '1plusX.com' +const PAPI_VERSION = 'v1.0'; +const LOG_PREFIX = '[1plusX RTD Module]: '; +const LEGACY_SITE_KEYWORDS_BIDDERS = ['appnexus']; +export const segtaxes = { + // cf. https://github.com/InteractiveAdvertisingBureau/openrtb/pull/108 + AUDIENCE: 526, + CONTENT: 527, +}; +// Functions +/** + * Extracts the parameters for 1plusX RTD module from the config object passed at instanciation + * @param {Object} moduleConfig Config object passed to the module + * @param {Object} reqBidsConfigObj Config object for the bidders; each adapter has its own entry + * @returns {Object} Extracted configuration parameters for the module + */ +export const extractConfig = (moduleConfig, reqBidsConfigObj) => { + // CustomerId + const customerId = deepAccess(moduleConfig, 'params.customerId'); + if (!customerId) { + throw new Error('Missing parameter customerId in moduleConfig'); + } + // Timeout + const tempTimeout = deepAccess(moduleConfig, 'params.timeout'); + const timeout = isNumber(tempTimeout) && tempTimeout > 300 ? tempTimeout : 1000; + + // Bidders + const biddersTemp = deepAccess(moduleConfig, 'params.bidders'); + if (!isArray(biddersTemp) || !biddersTemp.length) { + throw new Error('Missing parameter bidders in moduleConfig'); + } + + const adUnitBidders = reqBidsConfigObj.adUnits + .flatMap(({ bids }) => bids.map(({ bidder }) => bidder)) + .filter((e, i, a) => a.indexOf(e) === i); + if (!isArray(adUnitBidders) || !adUnitBidders.length) { + throw new Error('Missing parameter bidders in bidRequestConfig'); + } + + const bidders = biddersTemp.filter(bidder => adUnitBidders.includes(bidder)); + if (!bidders.length) { + throw new Error('No bidRequestConfig bidder found in moduleConfig bidders'); + } + + return { customerId, timeout, bidders }; +} + +/** + * Gets the URL of Profile Api from which targeting data will be fetched + * @param {Object} config + * @param {string} config.customerId + * @returns {string} URL to access 1plusX Profile API + */ +const getPapiUrl = ({ customerId }) => { + // https://[yourClientId].profiles.tagger.opecloud.com/[VERSION]/targeting?url= + const currentUrl = encodeURIComponent(window.location.href); + const papiUrl = `https://${customerId}.profiles.tagger.opecloud.com/${PAPI_VERSION}/targeting?url=${currentUrl}`; + return papiUrl; +} + +/** + * Fetches targeting data. It contains the audience segments & the contextual topics + * @param {string} papiUrl URL of profile API + * @returns {Promise} Promise object resolving with data fetched from Profile API + */ +const getTargetingDataFromPapi = (papiUrl) => { + return new Promise((resolve, reject) => { + const requestOptions = { + customHeaders: { + 'Accept': 'application/json' + } + } + const callbacks = { + success(responseText, response) { + resolve(JSON.parse(response.response)); + }, + error(error) { + reject(error); + } + }; + ajax(papiUrl, callbacks, null, requestOptions) + }) +} + +/** + * Prepares the update for the ORTB2 object + * @param {Object} targetingData Targeting data fetched from Profile API + * @param {string[]} segments Represents the audience segments of the user + * @param {string[]} topics Represents the topics of the page + * @returns {Object} Object describing the updates to make on bidder configs + */ +export const buildOrtb2Updates = ({ segments = [], topics = [] }, bidder) => { + // Currently appnexus bidAdapter doesn't support topics in `site.content.data.segment` + // Therefore, writing them in `site.keywords` until it's supported + // Other bidAdapters do fine with `site.content.data.segment` + const writeToLegacySiteKeywords = LEGACY_SITE_KEYWORDS_BIDDERS.includes(bidder); + if (writeToLegacySiteKeywords) { + const site = { + keywords: topics.join(',') + }; + return { site }; + } + + const userData = { + name: ORTB2_NAME, + segment: segments.map((segmentId) => ({ id: segmentId })) + }; + const siteContentData = { + name: ORTB2_NAME, + segment: topics.map((topicId) => ({ id: topicId })), + ext: { segtax: segtaxes.CONTENT } + } + return { userData, siteContentData }; +} + +/** + * Merges the targeting data with the existing config for bidder and updates + * @param {string} bidder Bidder for which to set config + * @param {Object} ortb2Updates Updates to be applied to bidder config + * @param {Object} bidderConfigs All current bidder configs + * @returns {Object} Updated bidder config + */ +export const updateBidderConfig = (bidder, ortb2Updates, bidderConfigs) => { + const { site, siteContentData, userData } = ortb2Updates; + const bidderConfigCopy = mergeDeep({}, bidderConfigs[bidder]); + + if (site) { + // Legacy : cf. comment on buildOrtb2Updates first lines + const currentSite = deepAccess(bidderConfigCopy, 'ortb2.site') + const updatedSite = mergeDeep(currentSite, site); + deepSetValue(bidderConfigCopy, 'ortb2.site', updatedSite); + } + + if (siteContentData) { + const siteDataPath = 'ortb2.site.content.data'; + const currentSiteContentData = deepAccess(bidderConfigCopy, siteDataPath) || []; + const updatedSiteContentData = [ + ...currentSiteContentData.filter(({ name }) => name != siteContentData.name), + siteContentData + ]; + deepSetValue(bidderConfigCopy, siteDataPath, updatedSiteContentData); + } + + if (userData) { + const userDataPath = 'ortb2.user.data'; + const currentUserData = deepAccess(bidderConfigCopy, userDataPath) || []; + const updatedUserData = [ + ...currentUserData.filter(({ name }) => name != userData.name), + userData + ]; + deepSetValue(bidderConfigCopy, userDataPath, updatedUserData); + } + + return bidderConfigCopy; +}; + +const setAppnexusAudiences = (audiences) => { + config.setConfig({ + appnexusAuctionKeywords: { + '1plusX': audiences, + }, + }); +} + +/** + * Updates bidder configs with the targeting data retreived from Profile API + * @param {Object} papiResponse Response from Profile API + * @param {Object} config Module configuration + * @param {string[]} config.bidders Bidders specified in module's configuration + */ +export const setTargetingDataToConfig = (papiResponse, { bidders }) => { + const bidderConfigs = config.getBidderConfig(); + const { s: segments, t: topics } = papiResponse; + + for (const bidder of bidders) { + const ortb2Updates = buildOrtb2Updates({ segments, topics }, bidder); + const updatedBidderConfig = updateBidderConfig(bidder, ortb2Updates, bidderConfigs); + if (updatedBidderConfig) { + config.setBidderConfig({ + bidders: [bidder], + config: updatedBidderConfig + }); + } + if (bidder === 'appnexus') { + // Do the legacy stuff for appnexus with segments + setAppnexusAudiences(segments); + } + } +} + +// Functions exported in submodule object +/** + * Init + * @param {Object} config Module configuration + * @param {boolean} userConsent + * @returns true + */ +const init = (config, userConsent) => { + return true; +} + +/** + * + * @param {Object} reqBidsConfigObj Bid request configuration object + * @param {Function} callback Called on completion + * @param {Object} moduleConfig Configuration for 1plusX RTD module + * @param {boolean} userConsent + */ +const getBidRequestData = (reqBidsConfigObj, callback, moduleConfig, userConsent) => { + try { + // Get the required config + const { customerId, bidders } = extractConfig(moduleConfig, reqBidsConfigObj); + // Get PAPI URL + const papiUrl = getPapiUrl({ customerId }) + // Call PAPI + getTargetingDataFromPapi(papiUrl) + .then((papiResponse) => { + logMessage(LOG_PREFIX, 'Get targeting data request successful'); + setTargetingDataToConfig(papiResponse, { bidders }); + callback(); + }) + .catch((error) => { + throw error; + }) + } catch (error) { + logError(LOG_PREFIX, error); + callback(); + } +} + +// The RTD submodule object to be exported +export const onePlusXSubmodule = { + name: MODULE_NAME, + init, + getBidRequestData +} + +// Register the onePlusXSubmodule as submodule of realTimeData +submodule(REAL_TIME_MODULE, onePlusXSubmodule); diff --git a/modules/1plusXRtdProvider.md b/modules/1plusXRtdProvider.md new file mode 100644 index 00000000000..75ad3b966d1 --- /dev/null +++ b/modules/1plusXRtdProvider.md @@ -0,0 +1,67 @@ +# 1plusX Real-time Data Submodule + +## Overview + + Module Name: 1plusX Rtd Provider + Module Type: Rtd Provider + Maintainer: dc-team-1px@triplelift.com + +## Description + +RTD provider for 1plusX. +Enriches the bidding object with Audience & Targeting data +Contact dc-team-1px@triplelift.com for information. + +## Usage + +### Build +``` +gulp build --modules="rtdModule,1plusXRtdProvider,appnexusBidAdapter,..." +``` + +> Note that the global RTD module, `rtdModule`, is a prerequisite of the 1plusX RTD module. + +### Configuration + +Use `setConfig` to instruct Prebid.js to initilize the 1plusX RTD module, as specified below. + +This module is configured as part of the `realTimeData.dataProviders` + +```javascript +var TIMEOUT = 1000; +pbjs.setConfig({ + realTimeData: { + auctionDelay: TIMEOUT, + dataProviders: [{ + name: '1plusX', + waitForIt: true, + params: { + customerId: 'acme', + bidders: ['appnexus', 'rubicon'], + timeout: TIMEOUT + } + }] + } +}); +``` + +### Parameters + +| Name | Type | Description | Notes | +| :---------------- | :------------ | :--------------------------------------------------------------- |:-------------------------------------------------------- | +| name | String | Real time data module name | Always '1plusX' | +| waitForIt | Boolean | Should be `true` if there's an `auctionDelay` defined (optional) | `false` | +| params | Object | | | +| params.customerId | String | Your 1plusX customer id | | +| params.bidders | Array | List of bidders for which you would like data to be set | | +| params.timeout | Integer | timeout (ms) | 1000ms | + +## Testing + +To view an example of how the 1plusX RTD module works : + +`gulp serve --modules=rtdModule,1plusXRtdProvider,appnexusBidAdapter,rubiconBidAdapter` + +and then point your browser at: + +`http://localhost:9999/integrationExamples/gpt/1plusXRtdProvider_example.html` diff --git a/modules/kargoAnalyticsAdapter.md b/modules/kargoAnalyticsAdapter.md new file mode 100644 index 00000000000..5a1e538902a --- /dev/null +++ b/modules/kargoAnalyticsAdapter.md @@ -0,0 +1,33 @@ +# Overview + +Module Name: Kargo Analytics Adapter +Module Type: Analytics Adapter +Maintainer: support@kargo.com + +# Description + +Analytics adapter for Kargo. Contact support@kargo.com for information. + +# Usage + +The simplest way to enable the analytics adapter is this + +```javascript +pbjs.enableAnalytics([{ + provider: 'kargo', + options: { + sampling: 100 // value out of 100 + } +}]); +``` + +# Test Parameters + +``` +{ + provider: 'kargo', + options: { + sampling: 100 + } +} +``` \ No newline at end of file diff --git a/modules/newspassidBidAdapter.js b/modules/newspassidBidAdapter.js new file mode 100644 index 00000000000..ee6ece2b033 --- /dev/null +++ b/modules/newspassidBidAdapter.js @@ -0,0 +1,649 @@ +import { logInfo, logError, deepAccess, logWarn, deepSetValue, isArray, contains, parseUrl } from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE } from '../src/mediaTypes.js'; +import {config} from '../src/config.js'; +import {getPriceBucketString} from '../src/cpmBucketManager.js'; +import {getRefererInfo} from '../src/refererDetection.js'; +const BIDDER_CODE = 'newspassid'; +const ORIGIN = 'https://bidder.newspassid.com' // applies only to auction & cookie +const AUCTIONURI = '/openrtb2/auction'; +const NEWSPASSCOOKIESYNC = '/static/load-cookie.html'; +const NEWSPASSVERSION = '1.0.1'; +export const spec = { + version: NEWSPASSVERSION, + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + cookieSyncBag: {publisherId: null, siteId: null, userIdObject: {}}, // variables we want to make available to cookie sync + propertyBag: {config: null, pageId: null, buildRequestsStart: 0, buildRequestsEnd: 0, endpointOverride: null}, /* allow us to store vars in instance scope - needs to be an object to be mutable */ + config_defaults: { + 'logId': 'NEWSPASSID', + 'bidder': 'newspassid', + 'auctionUrl': ORIGIN + AUCTIONURI, + 'cookieSyncUrl': ORIGIN + NEWSPASSCOOKIESYNC + }, + loadConfiguredData(bid) { + if (this.propertyBag.config) { return; } + this.propertyBag.config = JSON.parse(JSON.stringify(this.config_defaults)); + let bidder = bid.bidder || 'newspassid'; + this.propertyBag.config.logId = bidder.toUpperCase(); + this.propertyBag.config.bidder = bidder; + let bidderConfig = config.getConfig(bidder) || {}; + logInfo('got bidderConfig: ', JSON.parse(JSON.stringify(bidderConfig))); + let arrGetParams = this.getGetParametersAsObject(); + if (bidderConfig.endpointOverride) { + if (bidderConfig.endpointOverride.origin) { + this.propertyBag.endpointOverride = bidderConfig.endpointOverride.origin; + this.propertyBag.config.auctionUrl = bidderConfig.endpointOverride.origin + AUCTIONURI; + this.propertyBag.config.cookieSyncUrl = bidderConfig.endpointOverride.origin + NEWSPASSCOOKIESYNC; + } + if (bidderConfig.endpointOverride.cookieSyncUrl) { + this.propertyBag.config.cookieSyncUrl = bidderConfig.endpointOverride.cookieSyncUrl; + } + if (bidderConfig.endpointOverride.auctionUrl) { + this.propertyBag.endpointOverride = bidderConfig.endpointOverride.auctionUrl; + this.propertyBag.config.auctionUrl = bidderConfig.endpointOverride.auctionUrl; + } + } + try { + if (arrGetParams.hasOwnProperty('auction')) { + logInfo('GET: setting auction endpoint to: ' + arrGetParams.auction); + this.propertyBag.config.auctionUrl = arrGetParams.auction; + } + if (arrGetParams.hasOwnProperty('cookiesync')) { + logInfo('GET: setting cookiesync to: ' + arrGetParams.cookiesync); + this.propertyBag.config.cookieSyncUrl = arrGetParams.cookiesync; + } + } catch (e) {} + logInfo('set propertyBag.config to', this.propertyBag.config); + }, + getAuctionUrl() { + return this.propertyBag.config.auctionUrl; + }, + getCookieSyncUrl() { + return this.propertyBag.config.cookieSyncUrl; + }, + isBidRequestValid(bid) { + this.loadConfiguredData(bid); + logInfo('isBidRequestValid : ', config.getConfig(), bid); + let adUnitCode = bid.adUnitCode; // adunit[n].code + let err1 = 'VALIDATION FAILED : missing {param} : siteId, placementId and publisherId are REQUIRED' + if (!(bid.params.hasOwnProperty('placementId'))) { + logError(err1.replace('{param}', 'placementId'), adUnitCode); + return false; + } + if (!this.isValidPlacementId(bid.params.placementId)) { + logError('VALIDATION FAILED : placementId must be exactly 10 numeric characters', adUnitCode); + return false; + } + if (!(bid.params.hasOwnProperty('publisherId'))) { + logError(err1.replace('{param}', 'publisherId'), adUnitCode); + return false; + } + if (!(bid.params.publisherId).toString().match(/^[a-zA-Z0-9\-]{12}$/)) { + logError('VALIDATION FAILED : publisherId must be exactly 12 alphanumeric characters including hyphens', adUnitCode); + return false; + } + if (!(bid.params.hasOwnProperty('siteId'))) { + logError(err1.replace('{param}', 'siteId'), adUnitCode); + return false; + } + if (!(bid.params.siteId).toString().match(/^[0-9]{10}$/)) { + logError('VALIDATION FAILED : siteId must be exactly 10 numeric characters', adUnitCode); + return false; + } + if (bid.params.hasOwnProperty('customParams')) { + logError('VALIDATION FAILED : customParams should be renamed to customData', adUnitCode); + return false; + } + if (bid.params.hasOwnProperty('customData')) { + if (!Array.isArray(bid.params.customData)) { + logError('VALIDATION FAILED : customData is not an Array', adUnitCode); + return false; + } + if (bid.params.customData.length < 1) { + logError('VALIDATION FAILED : customData is an array but does not contain any elements', adUnitCode); + return false; + } + if (!(bid.params.customData[0]).hasOwnProperty('targeting')) { + logError('VALIDATION FAILED : customData[0] does not contain "targeting"', adUnitCode); + return false; + } + if (typeof bid.params.customData[0]['targeting'] != 'object') { + logError('VALIDATION FAILED : customData[0] targeting is not an object', adUnitCode); + return false; + } + } + return true; + }, + isValidPlacementId(placementId) { + return placementId.toString().match(/^[0-9]{10}$/); + }, + buildRequests(validBidRequests, bidderRequest) { + this.loadConfiguredData(validBidRequests[0]); + this.propertyBag.buildRequestsStart = new Date().getTime(); + logInfo(`buildRequests time: ${this.propertyBag.buildRequestsStart} v ${NEWSPASSVERSION} validBidRequests`, JSON.parse(JSON.stringify(validBidRequests)), 'bidderRequest', JSON.parse(JSON.stringify(bidderRequest))); + if (this.blockTheRequest()) { + return []; + } + let htmlParams = {'publisherId': '', 'siteId': ''}; + if (validBidRequests.length > 0) { + this.cookieSyncBag.userIdObject = Object.assign(this.cookieSyncBag.userIdObject, this.findAllUserIds(validBidRequests[0])); + this.cookieSyncBag.siteId = deepAccess(validBidRequests[0], 'params.siteId'); + this.cookieSyncBag.publisherId = deepAccess(validBidRequests[0], 'params.publisherId'); + htmlParams = validBidRequests[0].params; + } + logInfo('cookie sync bag', this.cookieSyncBag); + let singleRequest = config.getConfig('newspassid.singleRequest'); + singleRequest = singleRequest !== false; // undefined & true will be true + logInfo(`config newspassid.singleRequest : `, singleRequest); + let npRequest = {}; // we only want to set specific properties on this, not validBidRequests[0].params + logInfo('going to get ortb2 from bidder request...'); + let fpd = deepAccess(bidderRequest, 'ortb2', null); + logInfo('got fpd: ', fpd); + if (fpd && deepAccess(fpd, 'user')) { + logInfo('added FPD user object'); + npRequest.user = fpd.user; + } + const getParams = this.getGetParametersAsObject(); + const isTestMode = getParams['nptestmode'] || null; // this can be any string, it's used for testing ads + npRequest.device = {'w': window.innerWidth, 'h': window.innerHeight}; + let placementIdOverrideFromGetParam = this.getPlacementIdOverrideFromGetParam(); // null or string + let schain = null; + let tosendtags = validBidRequests.map(npBidRequest => { + var obj = {}; + let placementId = placementIdOverrideFromGetParam || this.getPlacementId(npBidRequest); // prefer to use a valid override param, else the bidRequest placement Id + obj.id = npBidRequest.bidId; // this causes an error if we change it to something else, even if you update the bidRequest object: "WARNING: Bidder newspass made bid for unknown request ID: mb7953.859498327448. Ignoring." + obj.tagid = placementId; + let parsed = parseUrl(getRefererInfo().page); + obj.secure = parsed.protocol === 'https' ? 1 : 0; + let arrBannerSizes = []; + if (!npBidRequest.hasOwnProperty('mediaTypes')) { + if (npBidRequest.hasOwnProperty('sizes')) { + logInfo('no mediaTypes detected - will use the sizes array in the config root'); + arrBannerSizes = npBidRequest.sizes; + } else { + logInfo('Cannot set sizes for banner type'); + } + } else { + if (npBidRequest.mediaTypes.hasOwnProperty(BANNER)) { + arrBannerSizes = npBidRequest.mediaTypes[BANNER].sizes; /* Note - if there is a sizes element in the config root it will be pushed into here */ + logInfo('setting banner size from the mediaTypes.banner element for bidId ' + obj.id + ': ', arrBannerSizes); + } + if (npBidRequest.mediaTypes.hasOwnProperty(NATIVE)) { + obj.native = npBidRequest.mediaTypes[NATIVE]; + logInfo('setting native object from the mediaTypes.native element: ' + obj.id + ':', obj.native); + } + } + if (arrBannerSizes.length > 0) { + obj.banner = { + topframe: 1, + w: arrBannerSizes[0][0] || 0, + h: arrBannerSizes[0][1] || 0, + format: arrBannerSizes.map(s => { + return {w: s[0], h: s[1]}; + }) + }; + } + obj.placementId = placementId; + deepSetValue(obj, 'ext.prebid', {'storedrequest': {'id': placementId}}); + obj.ext['newspassid'] = {}; + obj.ext['newspassid'].adUnitCode = npBidRequest.adUnitCode; // eg. 'mpu' + obj.ext['newspassid'].transactionId = npBidRequest.transactionId; // this is the transactionId PER adUnit, common across bidders for this unit + if (npBidRequest.params.hasOwnProperty('customData')) { + obj.ext['newspassid'].customData = npBidRequest.params.customData; + } + logInfo(`obj.ext.newspassid is `, obj.ext['newspassid']); + if (isTestMode != null) { + logInfo('setting isTestMode to ', isTestMode); + if (obj.ext['newspassid'].hasOwnProperty('customData')) { + for (let i = 0; i < obj.ext['newspassid'].customData.length; i++) { + obj.ext['newspassid'].customData[i]['targeting']['nptestmode'] = isTestMode; + } + } else { + obj.ext['newspassid'].customData = [{'settings': {}, 'targeting': {}}]; + obj.ext['newspassid'].customData[0].targeting['nptestmode'] = isTestMode; + } + } + if (fpd && deepAccess(fpd, 'site')) { + logInfo('adding fpd.site'); + if (deepAccess(obj, 'ext.newspassid.customData.0.targeting', false)) { + obj.ext.newspassid.customData[0].targeting = Object.assign(obj.ext.newspassid.customData[0].targeting, fpd.site); + } else { + deepSetValue(obj, 'ext.newspassid.customData.0.targeting', fpd.site); + } + } + if (!schain && deepAccess(npBidRequest, 'schain')) { + schain = npBidRequest.schain; + } + return obj; + }); + let extObj = {}; + extObj['newspassid'] = {}; + extObj['newspassid']['np_pb_v'] = NEWSPASSVERSION; + extObj['newspassid']['np_rw'] = placementIdOverrideFromGetParam ? 1 : 0; + if (validBidRequests.length > 0) { + let userIds = this.cookieSyncBag.userIdObject; // 2021-01-06 - slight optimisation - we've already found this info + if (userIds.hasOwnProperty('pubcid')) { + extObj['newspassid'].pubcid = userIds.pubcid; + } + } + extObj['newspassid'].pv = this.getPageId(); // attach the page ID that will be common to all auction calls for this page if refresh() is called + let whitelistAdserverKeys = config.getConfig('newspassid.np_whitelist_adserver_keys'); + let useWhitelistAdserverKeys = isArray(whitelistAdserverKeys) && whitelistAdserverKeys.length > 0; + extObj['newspassid']['np_kvp_rw'] = useWhitelistAdserverKeys ? 1 : 0; + if (getParams.hasOwnProperty('npf')) { extObj['newspassid']['npf'] = getParams.npf === 'true' || getParams.npf === '1' ? 1 : 0; } + if (getParams.hasOwnProperty('nppf')) { extObj['newspassid']['nppf'] = getParams.nppf === 'true' || getParams.nppf === '1' ? 1 : 0; } + if (getParams.hasOwnProperty('nprp') && getParams.nprp.match(/^[0-3]$/)) { extObj['newspassid']['nprp'] = parseInt(getParams.nprp); } + if (getParams.hasOwnProperty('npip') && getParams.npip.match(/^\d+$/)) { extObj['newspassid']['npip'] = parseInt(getParams.npip); } + if (this.propertyBag.endpointOverride != null) { extObj['newspassid']['origin'] = this.propertyBag.endpointOverride; } + let userExtEids = deepAccess(validBidRequests, '0.userIdAsEids', []); // generate the UserIDs in the correct format for UserId module + npRequest.site = { + 'publisher': {'id': htmlParams.publisherId}, + 'page': getRefererInfo().page, + 'id': htmlParams.siteId + }; + npRequest.test = config.getConfig('debug') ? 1 : 0; + if (bidderRequest && bidderRequest.uspConsent) { + logInfo('ADDING USP consent info'); + deepSetValue(npRequest, 'regs.ext.us_privacy', bidderRequest.uspConsent); + } else { + logInfo('WILL NOT ADD USP consent info; no bidderRequest.uspConsent.'); + } + if (schain) { // we set this while iterating over the bids + logInfo('schain found'); + deepSetValue(npRequest, 'source.ext.schain', schain); + } + if (config.getConfig('coppa') === true) { + deepSetValue(npRequest, 'regs.coppa', 1); + } + if (singleRequest) { + logInfo('buildRequests starting to generate response for a single request'); + npRequest.id = bidderRequest.auctionId; // Unique ID of the bid request, provided by the exchange. + npRequest.auctionId = bidderRequest.auctionId; // not sure if this should be here? + npRequest.imp = tosendtags; + npRequest.ext = extObj; + deepSetValue(npRequest, 'source.tid', bidderRequest.auctionId);// RTB 2.5 : tid is Transaction ID that must be common across all participants in this bid request (e.g., potentially multiple exchanges). + deepSetValue(npRequest, 'user.ext.eids', userExtEids); + var ret = { + method: 'POST', + url: this.getAuctionUrl(), + data: JSON.stringify(npRequest), + bidderRequest: bidderRequest + }; + logInfo('buildRequests request data for single = ', JSON.parse(JSON.stringify(npRequest))); + this.propertyBag.buildRequestsEnd = new Date().getTime(); + logInfo(`buildRequests going to return for single at time ${this.propertyBag.buildRequestsEnd} (took ${this.propertyBag.buildRequestsEnd - this.propertyBag.buildRequestsStart}ms): `, ret); + return ret; + } + let arrRet = tosendtags.map(imp => { + logInfo('buildRequests starting to generate non-single response, working on imp : ', imp); + let npRequestSingle = Object.assign({}, npRequest); + imp.ext['newspassid'].pageAuctionId = bidderRequest['auctionId']; // make a note in the ext object of what the original auctionId was, in the bidderRequest object + npRequestSingle.id = imp.ext['newspassid'].transactionId; // Unique ID of the bid request, provided by the exchange. + npRequestSingle.auctionId = imp.ext['newspassid'].transactionId; // not sure if this should be here? + npRequestSingle.imp = [imp]; + npRequestSingle.ext = extObj; + deepSetValue(npRequestSingle, 'source.tid', imp.ext['newspassid'].transactionId);// RTB 2.5 : tid is Transaction ID that must be common across all participants in this bid request (e.g., potentially multiple exchanges). + deepSetValue(npRequestSingle, 'user.ext.eids', userExtEids); + logInfo('buildRequests RequestSingle (for non-single) = ', npRequestSingle); + return { + method: 'POST', + url: this.getAuctionUrl(), + data: JSON.stringify(npRequestSingle), + bidderRequest: bidderRequest + }; + }); + this.propertyBag.buildRequestsEnd = new Date().getTime(); + logInfo(`buildRequests going to return for non-single at time ${this.propertyBag.buildRequestsEnd} (took ${this.propertyBag.buildRequestsEnd - this.propertyBag.buildRequestsStart}ms): `, arrRet); + return arrRet; + }, + interpretResponse(serverResponse, request) { + if (request && request.bidderRequest && request.bidderRequest.bids) { this.loadConfiguredData(request.bidderRequest.bids[0]); } + let startTime = new Date().getTime(); + logInfo(`interpretResponse time: ${startTime}. buildRequests done -> interpretResponse start was ${startTime - this.propertyBag.buildRequestsEnd}ms`); + logInfo(`serverResponse, request`, JSON.parse(JSON.stringify(serverResponse)), JSON.parse(JSON.stringify(request))); + serverResponse = serverResponse.body || {}; + if (!serverResponse.hasOwnProperty('seatbid')) { + return []; + } + if (typeof serverResponse.seatbid !== 'object') { + return []; + } + let arrAllBids = []; + let enhancedAdserverTargeting = config.getConfig('newspassid.enhancedAdserverTargeting'); + logInfo('enhancedAdserverTargeting', enhancedAdserverTargeting); + if (typeof enhancedAdserverTargeting == 'undefined') { + enhancedAdserverTargeting = true; + } + logInfo('enhancedAdserverTargeting', enhancedAdserverTargeting); + serverResponse.seatbid = injectAdIdsIntoAllBidResponses(serverResponse.seatbid); // we now make sure that each bid in the bidresponse has a unique (within page) adId attribute. + serverResponse.seatbid = this.removeSingleBidderMultipleBids(serverResponse.seatbid); + let whitelistAdserverKeys = config.getConfig('newspassid.np_whitelist_adserver_keys'); + let useWhitelistAdserverKeys = isArray(whitelistAdserverKeys) && whitelistAdserverKeys.length > 0; + for (let i = 0; i < serverResponse.seatbid.length; i++) { + let sb = serverResponse.seatbid[i]; + for (let j = 0; j < sb.bid.length; j++) { + let thisRequestBid = this.getBidRequestForBidId(sb.bid[j].impid, request.bidderRequest.bids); + logInfo(`seatbid:${i}, bid:${j} Going to set default w h for seatbid/bidRequest`, sb.bid[j], thisRequestBid); + const {defaultWidth, defaultHeight} = defaultSize(thisRequestBid); + let thisBid = this.addStandardProperties(sb.bid[j], defaultWidth, defaultHeight); + thisBid.meta = {advertiserDomains: thisBid.adomain || []}; + let bidType = deepAccess(thisBid, 'ext.prebid.type'); + logInfo(`this bid type is : ${bidType}`, j); + let adserverTargeting = {}; + if (enhancedAdserverTargeting) { + let allBidsForThisBidid = this.getAllBidsForBidId(thisBid.bidId, serverResponse.seatbid); + logInfo('Going to iterate allBidsForThisBidId', allBidsForThisBidid); + Object.keys(allBidsForThisBidid).forEach((bidderName, index, ar2) => { + logInfo(`adding adserverTargeting for ${bidderName} for bidId ${thisBid.bidId}`); + adserverTargeting['np_' + bidderName] = bidderName; + adserverTargeting['np_' + bidderName + '_crid'] = String(allBidsForThisBidid[bidderName].crid); + adserverTargeting['np_' + bidderName + '_adv'] = String(allBidsForThisBidid[bidderName].adomain); + adserverTargeting['np_' + bidderName + '_adId'] = String(allBidsForThisBidid[bidderName].adId); + adserverTargeting['np_' + bidderName + '_pb_r'] = getRoundedBid(allBidsForThisBidid[bidderName].price, allBidsForThisBidid[bidderName].ext.prebid.type); + if (allBidsForThisBidid[bidderName].hasOwnProperty('dealid')) { + adserverTargeting['np_' + bidderName + '_dealid'] = String(allBidsForThisBidid[bidderName].dealid); + } + }); + } else { + logInfo(`newspassid.enhancedAdserverTargeting is set to false, no per-bid keys will be sent to adserver.`); + } + let {seat: winningSeat, bid: winningBid} = this.getWinnerForRequestBid(thisBid.bidId, serverResponse.seatbid); + adserverTargeting['np_auc_id'] = String(request.bidderRequest.auctionId); + adserverTargeting['np_winner'] = String(winningSeat); + adserverTargeting['np_bid'] = 'true'; + if (enhancedAdserverTargeting) { + adserverTargeting['np_imp_id'] = String(winningBid.impid); + adserverTargeting['np_pb_r'] = getRoundedBid(winningBid.price, bidType); + adserverTargeting['np_adId'] = String(winningBid.adId); + adserverTargeting['np_size'] = `${winningBid.width}x${winningBid.height}`; + } + if (useWhitelistAdserverKeys) { // delete any un-whitelisted keys + logInfo('Going to filter out adserver targeting keys not in the whitelist: ', whitelistAdserverKeys); + Object.keys(adserverTargeting).forEach(function(key) { if (whitelistAdserverKeys.indexOf(key) === -1) { delete adserverTargeting[key]; } }); + } + thisBid.adserverTargeting = adserverTargeting; + arrAllBids.push(thisBid); + } + } + let endTime = new Date().getTime(); + logInfo(`interpretResponse going to return at time ${endTime} (took ${endTime - startTime}ms) Time from buildRequests Start -> interpretRequests End = ${endTime - this.propertyBag.buildRequestsStart}ms`, arrAllBids); + return arrAllBids; + }, + removeSingleBidderMultipleBids(seatbid) { + var ret = []; + for (let i = 0; i < seatbid.length; i++) { + let sb = seatbid[i]; + var retSeatbid = {'seat': sb.seat, 'bid': []}; + var bidIds = []; + for (let j = 0; j < sb.bid.length; j++) { + var candidate = sb.bid[j]; + if (contains(bidIds, candidate.impid)) { + continue; // we've already fully assessed this impid, found the highest bid from this seat for it + } + bidIds.push(candidate.impid); + for (let k = j + 1; k < sb.bid.length; k++) { + if (sb.bid[k].impid === candidate.impid && sb.bid[k].price > candidate.price) { + candidate = sb.bid[k]; + } + } + retSeatbid.bid.push(candidate); + } + ret.push(retSeatbid); + } + return ret; + }, + getUserSyncs(optionsType, serverResponse, gdprConsent, usPrivacy) { + logInfo('getUserSyncs optionsType', optionsType, 'serverResponse', serverResponse, 'usPrivacy', usPrivacy, 'cookieSyncBag', this.cookieSyncBag); + if (!serverResponse || serverResponse.length === 0) { + return []; + } + if (optionsType.iframeEnabled) { + var arrQueryString = []; + if (config.getConfig('debug')) { + arrQueryString.push('pbjs_debug=true'); + } + arrQueryString.push('usp_consent=' + (usPrivacy || '')); + for (let keyname in this.cookieSyncBag.userIdObject) { + arrQueryString.push(keyname + '=' + this.cookieSyncBag.userIdObject[keyname]); + } + arrQueryString.push('publisherId=' + this.cookieSyncBag.publisherId); + arrQueryString.push('siteId=' + this.cookieSyncBag.siteId); + arrQueryString.push('cb=' + Date.now()); + arrQueryString.push('bidder=' + this.propertyBag.config.bidder); + var strQueryString = arrQueryString.join('&'); + if (strQueryString.length > 0) { + strQueryString = '?' + strQueryString; + } + logInfo('getUserSyncs going to return cookie sync url : ' + this.getCookieSyncUrl() + strQueryString); + return [{ + type: 'iframe', + url: this.getCookieSyncUrl() + strQueryString + }]; + } + }, + getBidRequestForBidId(bidId, arrBids) { + for (let i = 0; i < arrBids.length; i++) { + if (arrBids[i].bidId === bidId) { // bidId in the request comes back as impid in the seatbid bids + return arrBids[i]; + } + } + return null; + }, + findAllUserIds(bidRequest) { + var ret = {}; + let searchKeysSingle = ['pubcid', 'tdid', 'idl_env', 'criteoId', 'lotamePanoramaId', 'fabrickId']; + if (bidRequest.hasOwnProperty('userId')) { + for (let arrayId in searchKeysSingle) { + let key = searchKeysSingle[arrayId]; + if (bidRequest.userId.hasOwnProperty(key)) { + if (typeof (bidRequest.userId[key]) == 'string') { + ret[key] = bidRequest.userId[key]; + } else if (typeof (bidRequest.userId[key]) == 'object') { + logError(`WARNING: findAllUserIds had to use first key in user object to get value for bid.userId key: ${key}. Prebid adapter should be updated.`); + ret[key] = bidRequest.userId[key][Object.keys(bidRequest.userId[key])[0]]; // cannot use Object.values + } else { + logError(`failed to get string key value for userId : ${key}`); + } + } + } + let lipbid = deepAccess(bidRequest.userId, 'lipb.lipbid'); + if (lipbid) { + ret['lipb'] = {'lipbid': lipbid}; + } + let id5id = deepAccess(bidRequest.userId, 'id5id.uid'); + if (id5id) { + ret['id5id'] = id5id; + } + let parrableId = deepAccess(bidRequest.userId, 'parrableId.eid'); + if (parrableId) { + ret['parrableId'] = parrableId; + } + let sharedid = deepAccess(bidRequest.userId, 'sharedid.id'); + if (sharedid) { + ret['sharedid'] = sharedid; + } + } + if (!ret.hasOwnProperty('pubcid')) { + let pubcid = deepAccess(bidRequest, 'crumbs.pubcid'); + if (pubcid) { + ret['pubcid'] = pubcid; // if built with old pubCommonId module + } + } + return ret; + }, + getPlacementId(bidRequest) { + return (bidRequest.params.placementId).toString(); + }, + getPlacementIdOverrideFromGetParam() { + let arr = this.getGetParametersAsObject(); + if (arr.hasOwnProperty('npstoredrequest')) { + if (this.isValidPlacementId(arr['npstoredrequest'])) { + logInfo(`using GET npstoredrequest ` + arr['npstoredrequest'] + ' to replace placementId'); + return arr['npstoredrequest']; + } else { + logError(`GET npstoredrequest FAILED VALIDATION - will not use it`); + } + } + return null; + }, + getGetParametersAsObject() { + let parsed = parseUrl(getRefererInfo().page); + logInfo('getGetParametersAsObject found:', parsed.search); + return parsed.search; + }, + blockTheRequest() { + let npRequest = config.getConfig('newspassid.np_request'); + if (typeof npRequest == 'boolean' && !npRequest) { + logWarn(`Will not allow auction : np_request is set to false`); + return true; + } + return false; + }, + getPageId: function() { + if (this.propertyBag.pageId == null) { + let randPart = ''; + let allowable = '0123456789abcdefghijklmnopqrstuvwxyz'; + for (let i = 20; i > 0; i--) { + randPart += allowable[Math.floor(Math.random() * 36)]; + } + this.propertyBag.pageId = new Date().getTime() + '_' + randPart; + } + return this.propertyBag.pageId; + }, + addStandardProperties(seatBid, defaultWidth, defaultHeight) { + seatBid.cpm = seatBid.price; + seatBid.bidId = seatBid.impid; + seatBid.requestId = seatBid.impid; + seatBid.width = seatBid.w || defaultWidth; + seatBid.height = seatBid.h || defaultHeight; + seatBid.ad = seatBid.adm; + seatBid.netRevenue = true; + seatBid.creativeId = seatBid.crid; + seatBid.currency = 'USD'; + seatBid.ttl = 300; + return seatBid; + }, + getWinnerForRequestBid(requestBidId, serverResponseSeatBid) { + let thisBidWinner = null; + let winningSeat = null; + for (let j = 0; j < serverResponseSeatBid.length; j++) { + let theseBids = serverResponseSeatBid[j].bid; + let thisSeat = serverResponseSeatBid[j].seat; + for (let k = 0; k < theseBids.length; k++) { + if (theseBids[k].impid === requestBidId) { + if ((thisBidWinner == null) || (thisBidWinner.price < theseBids[k].price)) { + thisBidWinner = theseBids[k]; + winningSeat = thisSeat; + break; + } + } + } + } + return {'seat': winningSeat, 'bid': thisBidWinner}; + }, + getAllBidsForBidId(matchBidId, serverResponseSeatBid) { + let objBids = {}; + for (let j = 0; j < serverResponseSeatBid.length; j++) { + let theseBids = serverResponseSeatBid[j].bid; + let thisSeat = serverResponseSeatBid[j].seat; + for (let k = 0; k < theseBids.length; k++) { + if (theseBids[k].impid === matchBidId) { + if (objBids.hasOwnProperty(thisSeat)) { // > 1 bid for an adunit from a bidder - only use the one with the highest bid + if (objBids[thisSeat]['price'] < theseBids[k].price) { + objBids[thisSeat] = theseBids[k]; + } + } else { + objBids[thisSeat] = theseBids[k]; + } + } + } + } + return objBids; + } +}; +export function injectAdIdsIntoAllBidResponses(seatbid) { + logInfo('injectAdIdsIntoAllBidResponses', seatbid); + for (let i = 0; i < seatbid.length; i++) { + let sb = seatbid[i]; + for (let j = 0; j < sb.bid.length; j++) { + sb.bid[j]['adId'] = `${sb.bid[j]['impid']}-${i}-np-${j}`; + } + } + return seatbid; +} +export function checkDeepArray(Arr) { + if (Array.isArray(Arr)) { + if (Array.isArray(Arr[0])) { + return Arr[0]; + } else { + return Arr; + } + } else { + return Arr; + } +} +export function defaultSize(thebidObj) { + if (!thebidObj) { + logInfo('defaultSize received empty bid obj! going to return fixed default size'); + return { + 'defaultHeight': 250, + 'defaultWidth': 300 + }; + } + const {sizes} = thebidObj; + const returnObject = {}; + returnObject.defaultWidth = checkDeepArray(sizes)[0]; + returnObject.defaultHeight = checkDeepArray(sizes)[1]; + return returnObject; +} +export function getRoundedBid(price, mediaType) { + const mediaTypeGranularity = config.getConfig(`mediaTypePriceGranularity.${mediaType}`); // might be string or object or nothing; if set then this takes precedence over 'priceGranularity' + let objBuckets = config.getConfig('customPriceBucket'); // this is always an object - {} if strBuckets is not 'custom' + let strBuckets = config.getConfig('priceGranularity'); // priceGranularity value, always a string ** if priceGranularity is set to an object then it's always 'custom' ** + let theConfigObject = getGranularityObject(mediaType, mediaTypeGranularity, strBuckets, objBuckets); + let theConfigKey = getGranularityKeyName(mediaType, mediaTypeGranularity, strBuckets); + logInfo('getRoundedBid. price:', price, 'mediaType:', mediaType, 'configkey:', theConfigKey, 'configObject:', theConfigObject, 'mediaTypeGranularity:', mediaTypeGranularity, 'strBuckets:', strBuckets); + let priceStringsObj = getPriceBucketString( + price, + theConfigObject, + config.getConfig('currency.granularityMultiplier') + ); + logInfo('priceStringsObj', priceStringsObj); + let granularityNamePriceStringsKeyMapping = { + 'medium': 'med', + 'custom': 'custom', + 'high': 'high', + 'low': 'low', + 'dense': 'dense' + }; + if (granularityNamePriceStringsKeyMapping.hasOwnProperty(theConfigKey)) { + let priceStringsKey = granularityNamePriceStringsKeyMapping[theConfigKey]; + logInfo('getRoundedBid: looking for priceStringsKey:', priceStringsKey); + return priceStringsObj[priceStringsKey]; + } + return priceStringsObj['auto']; +} +export function getGranularityKeyName(mediaType, mediaTypeGranularity, strBuckets) { + if (typeof mediaTypeGranularity === 'string') { + return mediaTypeGranularity; + } + if (typeof mediaTypeGranularity === 'object') { + return 'custom'; + } + if (typeof strBuckets === 'string') { + return strBuckets; + } + return 'auto'; // fall back to a default key - should literally never be needed. +} +export function getGranularityObject(mediaType, mediaTypeGranularity, strBuckets, objBuckets) { + if (typeof mediaTypeGranularity === 'object') { + return mediaTypeGranularity; + } + if (strBuckets === 'custom') { + return objBuckets; + } + return ''; +} +registerBidder(spec); +logInfo(`*BidAdapter ${NEWSPASSVERSION} was loaded`); diff --git a/modules/openxOrtbBidAdapter.js b/modules/openxOrtbBidAdapter.js new file mode 100644 index 00000000000..1885f5122eb --- /dev/null +++ b/modules/openxOrtbBidAdapter.js @@ -0,0 +1,349 @@ +import {config} from '../src/config.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import * as utils from '../src/utils.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {includes} from '../src/polyfill.js'; + +const bidderConfig = 'hb_pb_ortb'; +const bidderVersion = '1.0'; +const VIDEO_TARGETING = ['startdelay', 'mimes', 'minduration', 'maxduration', 'delivery', + 'startdelay', 'skip', 'playbackmethod', 'api', 'protocol', 'boxingallowed', 'maxextended', + 'linearity', 'delivery', 'protocols', 'placement', 'minbitrate', 'maxbitrate', 'battr', 'ext']; +export const REQUEST_URL = 'https://rtb.openx.net/openrtbb/prebidjs'; +export const SYNC_URL = 'https://u.openx.net/w/1.0/pd'; +export const DEFAULT_PH = '2d1251ae-7f3a-47cf-bd2a-2f288854a0ba'; +export const spec = { + code: 'openx', + supportedMediaTypes: [BANNER, VIDEO], + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs, + transformBidParams +}; + +registerBidder(spec); + +function transformBidParams(params, isOpenRtb) { + return utils.convertTypes({ + 'unit': 'string', + 'customFloor': 'number' + }, params); +} + +function isBidRequestValid(bidRequest) { + const hasDelDomainOrPlatform = bidRequest.params.delDomain || + bidRequest.params.platform; + + if (utils.deepAccess(bidRequest, 'mediaTypes.banner') && + hasDelDomainOrPlatform) { + return !!bidRequest.params.unit || + utils.deepAccess(bidRequest, 'mediaTypes.banner.sizes.length') > 0; + } + + return !!(bidRequest.params.unit && hasDelDomainOrPlatform); +} + +function buildRequests(bids, bidderRequest) { + let videoBids = bids.filter(bid => isVideoBid(bid)); + let bannerBids = bids.filter(bid => isBannerBid(bid)); + let requests = bannerBids.length ? [createBannerRequest(bannerBids, bidderRequest)] : []; + videoBids.forEach(bid => { + requests.push(createVideoRequest(bid, bidderRequest)); + }); + return requests; +} + +function createBannerRequest(bids, bidderRequest) { + let data = getBaseRequest(bids[0], bidderRequest); + data.imp = bids.map(bid => { + const floor = getFloor(bid, BANNER); + let imp = { + id: bid.bidId, + tagid: bid.params.unit, + banner: { + format: toFormat(bid.mediaTypes.banner.sizes), + topframe: utils.inIframe() ? 0 : 1 + }, + ext: {divid: bid.adUnitCode} + }; + enrichImp(imp, bid, floor); + return imp; + }); + return { + method: 'POST', + url: REQUEST_URL, + data: data + } +} + +function toFormat(sizes) { + return sizes.map((s) => { + return { w: s[0], h: s[1] }; + }); +} + +function enrichImp(imp, bid, floor) { + if (bid.params.customParams) { + utils.deepSetValue(imp, 'ext.customParams', bid.params.customParams); + } + if (floor > 0) { + imp.bidfloor = floor; + imp.bidfloorcur = 'USD'; + } else if (bid.params.customFloor) { + imp.bidfloor = bid.params.customFloor; + } + if (bid.ortb2Imp && bid.ortb2Imp.ext && bid.ortb2Imp.ext.data) { + imp.ext.data = bid.ortb2Imp.ext.data; + } +} + +function createVideoRequest(bid, bidderRequest) { + let width; + let height; + const videoMediaType = utils.deepAccess(bid, `mediaTypes.video`); + const playerSize = utils.deepAccess(bid, 'mediaTypes.video.playerSize'); + const context = utils.deepAccess(bid, 'mediaTypes.video.context'); + const floor = getFloor(bid, VIDEO); + + // normalize config for video size + if (utils.isArray(bid.sizes) && bid.sizes.length === 2 && !utils.isArray(bid.sizes[0])) { + width = parseInt(bid.sizes[0], 10); + height = parseInt(bid.sizes[1], 10); + } else if (utils.isArray(bid.sizes) && utils.isArray(bid.sizes[0]) && bid.sizes[0].length === 2) { + width = parseInt(bid.sizes[0][0], 10); + height = parseInt(bid.sizes[0][1], 10); + } else if (utils.isArray(playerSize) && playerSize.length === 2) { + width = parseInt(playerSize[0], 10); + height = parseInt(playerSize[1], 10); + } + + let data = getBaseRequest(bid, bidderRequest); + data.imp = [{ + id: bid.bidId, + tagid: bid.params.unit, + video: { + w: width, + h: height, + topframe: utils.inIframe() ? 0 : 1 + }, + ext: {divid: bid.adUnitCode} + }]; + + enrichImp(data.imp[0], bid, floor); + + if (context) { + if (context === 'instream') { + data.imp[0].video.placement = 1; + } else if (context === 'outstream') { + data.imp[0].video.placement = 4; + } + } + + // backward compatability for video params + let videoParams = bid.params.video || bid.params.openrtb || {}; + if (utils.isArray(videoParams.imp)) { + videoParams = videoParams[0].video; + } + + Object.keys(videoParams) + .filter(param => includes(VIDEO_TARGETING, param)) + .forEach(param => data.imp[0].video[param] = videoParams[param]); + Object.keys(videoMediaType) + .filter(param => includes(VIDEO_TARGETING, param)) + .forEach(param => data.imp[0].video[param] = videoMediaType[param]); + + return { + method: 'POST', + url: REQUEST_URL, + data: data + } +} + +function getBaseRequest(bid, bidderRequest) { + let req = { + id: bidderRequest.auctionId, + cur: [config.getConfig('currency.adServerCurrency') || 'USD'], + at: 1, + tmax: config.getConfig('bidderTimeout'), + site: { + page: config.getConfig('pageUrl') || bidderRequest.refererInfo.referer + }, + regs: { + coppa: (config.getConfig('coppa') === true || bid.params.coppa) ? 1 : 0, + }, + device: { + dnt: (utils.getDNT() || bid.params.doNotTrack) ? 1 : 0, + h: screen.height, + w: screen.width, + ua: window.navigator.userAgent, + language: window.navigator.language.split('-').shift() + }, + ext: { + bc: `${bidderConfig}_${bidderVersion}` + } + }; + + if (bid.params.platform) { + utils.deepSetValue(req, 'ext.platform', bid.params.platform); + } + if (bid.params.delDomain) { + utils.deepSetValue(req, 'ext.delDomain', bid.params.delDomain); + } + if (bid.params.test) { + req.test = 1 + } + if (bidderRequest.gdprConsent) { + if (bidderRequest.gdprConsent.gdprApplies !== undefined) { + utils.deepSetValue(req, 'regs.ext.gdpr', bidderRequest.gdprConsent.gdprApplies === true ? 1 : 0); + } + if (bidderRequest.gdprConsent.consentString !== undefined) { + utils.deepSetValue(req, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + } + if (bidderRequest.gdprConsent.addtlConsent !== undefined) { + utils.deepSetValue(req, 'user.ext.ConsentedProvidersSettings.consented_providers', bidderRequest.gdprConsent.addtlConsent); + } + } + if (bidderRequest.uspConsent) { + utils.deepSetValue(req, 'regs.ext.us_privacy', bidderRequest.uspConsent); + } + if (bid.schain) { + utils.deepSetValue(req, 'source.ext.schain', bid.schain); + } + if (bid.userIdAsEids) { + utils.deepSetValue(req, 'user.ext.eids', bid.userIdAsEids); + } + const commonFpd = bidderRequest.ortb2 || {}; + if (commonFpd.site) { + utils.mergeDeep(req, {site: commonFpd.site}); + } + if (commonFpd.user) { + utils.mergeDeep(req, {user: commonFpd.user}); + } + return req; +} + +function isVideoBid(bid) { + return utils.deepAccess(bid, 'mediaTypes.video'); +} + +function isBannerBid(bid) { + return utils.deepAccess(bid, 'mediaTypes.banner') || !isVideoBid(bid); +} + +function getFloor(bid, mediaType) { + let floor = 0; + + if (typeof bid.getFloor === 'function') { + const floorInfo = bid.getFloor({ + currency: 'USD', + mediaType: mediaType, + size: '*' + }); + + if (typeof floorInfo === 'object' && + floorInfo.currency === 'USD' && + !isNaN(parseFloat(floorInfo.floor))) { + floor = Math.max(floor, parseFloat(floorInfo.floor)); + } + } + + return floor; +} + +function interpretResponse(resp, req) { + // pass these from request to the responses for use in userSync + if (req.data.ext) { + if (req.data.ext.delDomain) { + utils.deepSetValue(resp, 'body.ext.delDomain', req.data.ext.delDomain); + } + if (req.data.ext.platform) { + utils.deepSetValue(resp, 'body.ext.platform', req.data.ext.platform); + } + } + + const respBody = resp.body; + if ('nbr' in respBody) { + return []; + } + + let bids = []; + respBody.seatbid.forEach(seatbid => { + bids = [...bids, ...seatbid.bid.map(bid => { + let response = { + requestId: bid.impid, + cpm: bid.price, + width: bid.w, + height: bid.h, + creativeId: bid.crid, + dealId: bid.dealid, + currency: respBody.cur || 'USD', + netRevenue: true, + ttl: 300, + mediaType: 'banner' in req.data.imp[0] ? BANNER : VIDEO, + meta: { advertiserDomains: bid.adomain } + }; + + if (response.mediaType === VIDEO) { + if (bid.nurl) { + response.vastUrl = bid.nurl; + } else { + response.vastXml = bid.adm; + } + } else { + response.ad = bid.adm; + } + + if (bid.ext) { + response.meta.networkId = bid.ext.dsp_id; + response.meta.advertiserId = bid.ext.buyer_id; + response.meta.brandId = bid.ext.brand_id; + } + + if (respBody.ext && respBody.ext.paf) { + response.meta.paf = respBody.ext.paf; + response.meta.paf.content_id = utils.deepAccess(bid, 'ext.paf.content_id'); + } + + return response + })]; + }); + + return bids; +} + +/** + * @param syncOptions + * @param responses + * @param gdprConsent + * @param uspConsent + * @return {{type: (string), url: (*|string)}[]} + */ +function getUserSyncs(syncOptions, responses, gdprConsent, uspConsent) { + if (syncOptions.iframeEnabled || syncOptions.pixelEnabled) { + let pixelType = syncOptions.iframeEnabled ? 'iframe' : 'image'; + let queryParamStrings = []; + let syncUrl = SYNC_URL; + if (gdprConsent) { + queryParamStrings.push('gdpr=' + (gdprConsent.gdprApplies ? 1 : 0)); + queryParamStrings.push('gdpr_consent=' + encodeURIComponent(gdprConsent.consentString || '')); + } + if (uspConsent) { + queryParamStrings.push('us_privacy=' + encodeURIComponent(uspConsent)); + } + if (responses.length > 0 && responses[0].body && responses[0].body.ext) { + const ext = responses[0].body.ext; + if (ext.delDomain) { + syncUrl = `https://${ext.delDomain}/w/1.0/pd` + } else if (ext.platform) { + queryParamStrings.push('ph=' + ext.platform) + } + } else { + queryParamStrings.push('ph=' + DEFAULT_PH) + } + return [{ + type: pixelType, + url: `${syncUrl}${queryParamStrings.length > 0 ? '?' + queryParamStrings.join('&') : ''}` + }]; + } +} diff --git a/modules/openxOrtbBidAdapter.md b/modules/openxOrtbBidAdapter.md new file mode 100644 index 00000000000..fd926b27b9f --- /dev/null +++ b/modules/openxOrtbBidAdapter.md @@ -0,0 +1,105 @@ +# Overview + +``` +Module Name: OpenX OpenRTB Bidder Adapter +Module Type: Bidder Adapter +Maintainer: team-openx@openx.com +``` + +# Description + +This is an updated version of the OpenX bid adapter which calls our new serving architecture. +Publishers are welcome to test this adapter and give feedback. Please note you should only include either openxBidAdapter or openxOrtbBidAdapter in your build. + +# Bid Parameters +## Banner + +| Name | Scope | Type | Description | Example +| ---- | ----- | ---- | ----------- | ------- +| `delDomain` or `platform` | required | String | OpenX delivery domain or platform id provided by your OpenX representative. | "PUBLISHER-d.openx.net" or "555not5a-real-plat-form-id0123456789" +| `unit` | required | String | OpenX ad unit ID provided by your OpenX representative. | "1611023122" +| `customParams` | optional | Object | User-defined targeting key-value pairs. customParams applies to a specific unit. | `{key1: "v1", key2: ["v2","v3"]}` +| `customFloor` | optional | Number | Minimum price in USD. customFloor applies to a specific unit. For example, use the following value to set a $1.50 floor: 1.50

**WARNING:**
Misuse of this parameter can impact revenue | 1.50 +| `doNotTrack` | optional | Boolean | Prevents advertiser from using data for this user.

**WARNING:**
Request-level setting. May impact revenue. | true +| `coppa` | optional | Boolean | Enables Child's Online Privacy Protection Act (COPPA) regulations. | true + +## Video + +| Name | Scope | Type | Description | Example +| ---- | ----- | ---- | ----------- | ------- +| `unit` | required | String | OpenX ad unit ID provided by your OpenX representative. | "1611023122" +| `delDomain` | required | String | OpenX delivery domain provided by your OpenX representative. | "PUBLISHER-d.openx.net" +| `video` | optional | OpenRTB video subtypes | Alternatively can be added under adUnit.mediaTypes.video | `{ video: {mimes: ['video/mp4']}` + + +# Example +```javascript +var adUnits = [ + { + code: 'test-div', + sizes: [[728, 90]], // a display size + mediaTypes: {'banner': {}}, + bids: [ + { + bidder: 'openx', + params: { + unit: '539439964', + delDomain: 'se-demo-d.openx.net', + customParams: { + key1: 'v1', + key2: ['v2', 'v3'] + }, + } + }, { + bidder: 'openx', + params: { + unit: '539439964', + platform: 'a3aece0c-9e80-4316-8deb-faf804779bd1', + customParams: { + key1: 'v1', + key2: ['v2', 'v3'] + }, + } + } + ] + }, + { + code: 'video1', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream' + } + }, + bids: [{ + bidder: 'openx', + params: { + unit: '1611023124', + delDomain: 'PUBLISHER-d.openx.net', + video: { + mimes: ['video/x-ms-wmv, video/mp4'] + } + } + }] + } +]; +``` + +# Configuration +Add the following code to enable user syncing. By default, Prebid.js version 0.34.0+ turns off user syncing through iframes. +OpenX strongly recommends enabling user syncing through iframes. This functionality improves DSP user match rates and increases the +OpenX bid rate and bid price. Be sure to call `pbjs.setConfig()` only once. + +```javascript +pbjs.setConfig({ + userSync: { + iframeEnabled: true + } +}); +``` + +# Additional Details +[Banner Ads](https://docs.openx.com/publishers/prebid-adapter-web/) + +[Video Ads](https://docs.openx.com/publishers/prebid-adapter-video/) + diff --git a/modules/rasBidAdapter.js b/modules/rasBidAdapter.js new file mode 100644 index 00000000000..d9e6b6a9126 --- /dev/null +++ b/modules/rasBidAdapter.js @@ -0,0 +1,149 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; +import { isEmpty, getAdUnitSizes, parseSizesInput, deepAccess } from '../src/utils.js'; + +const BIDDER_CODE = 'ras'; +const VERSION = '1.0'; + +const getEndpoint = (network) => `https://csr.onet.pl/${encodeURIComponent(network)}/csr-006/csr.json?`; + +function parseParams(params, bidderRequest) { + const newParams = {}; + const du = deepAccess(bidderRequest, 'refererInfo.page'); + const dr = deepAccess(bidderRequest, 'refererInfo.ref'); + + if (du) { + newParams.du = du; + } + if (dr) { + newParams.dr = dr; + } + const pageContext = params.pageContext; + if (!pageContext) { + return newParams; + } + if (pageContext.du) { + newParams.du = pageContext.du; + } + if (pageContext.dr) { + newParams.dr = pageContext.dr; + } + if (pageContext.dv) { + newParams.DV = pageContext.dv; + } + if (pageContext.keyWords && Array.isArray(pageContext.keyWords)) { + newParams.kwrd = pageContext.keyWords.join('+'); + } + if (pageContext.capping) { + newParams.local_capping = pageContext.capping; + } + if (pageContext.keyValues && typeof pageContext.keyValues === 'object') { + for (const param in pageContext.keyValues) { + if (pageContext.keyValues.hasOwnProperty(param)) { + const kvName = 'kv' + param; + newParams[kvName] = pageContext.keyValues[param]; + } + } + } + return newParams; +} + +const buildBid = (ad) => { + if (ad.type === 'empty') { + return null; + } + return { + requestId: ad.id, + cpm: ad.bid_rate ? ad.bid_rate.toFixed(2) : 0, + width: ad.width || 0, + height: ad.height || 0, + ttl: 300, + creativeId: ad.adid ? parseInt(ad.adid.split(',')[2], 10) : 0, + netRevenue: true, + currency: ad.currency || 'USD', + dealId: null, + meta: { + mediaType: BANNER + }, + ad: ad.html || null + }; +}; + +const getContextParams = (bidRequests, bidderRequest) => { + const bid = bidRequests[0]; + const { params } = bid; + const requestParams = { + site: params.site, + area: params.area, + cre_format: 'html', + systems: 'das', + kvprver: VERSION, + ems_url: 1, + bid_rate: 1, + ...parseParams(params, bidderRequest) + }; + return Object.keys(requestParams).map((key) => encodeURIComponent(key) + '=' + encodeURIComponent(requestParams[key])).join('&'); +}; + +const getSlots = (bidRequests) => { + let queryString = ''; + const batchSize = bidRequests.length; + for (let i = 0; i < batchSize; i++) { + const adunit = bidRequests[i]; + const sizes = parseSizesInput(getAdUnitSizes(adunit)).join(','); + queryString += `&slot${i}=${encodeURIComponent(adunit.params.slot)}&id${i}=${encodeURIComponent(adunit.bidId)}&composition${i}=CHILD`; + if (sizes.length) { + queryString += `&iusizes${i}=${encodeURIComponent(sizes)}`; + } + } + return queryString; +}; + +const getGdprParams = (bidderRequest) => { + const gdprApplies = deepAccess(bidderRequest, 'gdprConsent.gdprApplies'); + let consentString = deepAccess(bidderRequest, 'gdprConsent.consentString'); + let queryString = ''; + if (gdprApplies !== undefined) { + queryString += `&gdpr_applies=${encodeURIComponent(gdprApplies)}`; + } + if (consentString !== undefined) { + queryString += `&euconsent=${encodeURIComponent(consentString)}`; + } + return queryString; +}; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + + isBidRequestValid: function (bidRequest) { + if (!bidRequest || !bidRequest.params || typeof bidRequest.params !== 'object') { + return; + } + const { params } = bidRequest; + return Boolean(params.network && params.site && params.area && params.slot); + }, + + buildRequests: function (bidRequests, bidderRequest) { + const slotsQuery = getSlots(bidRequests); + const contextQuery = getContextParams(bidRequests, bidderRequest); + const gdprQuery = getGdprParams(bidderRequest); + const bidIds = bidRequests.map((bid) => ({ slot: bid.params.slot, bidId: bid.bidId })); + const network = bidRequests[0].params.network; + return [{ + method: 'GET', + url: getEndpoint(network) + contextQuery + slotsQuery + gdprQuery, + bidIds: bidIds + }]; + }, + + interpretResponse: function (serverResponse, bidRequest) { + const response = serverResponse.body; + if (!response || !response.ads || response.ads.length === 0) { + return []; + } + return response.ads.map(buildBid).filter((bid) => !isEmpty(bid)); + } +}; + +registerBidder(spec); diff --git a/modules/rasBidAdapter.md b/modules/rasBidAdapter.md new file mode 100644 index 00000000000..5cf75c3446d --- /dev/null +++ b/modules/rasBidAdapter.md @@ -0,0 +1,50 @@ +# Overview + +``` +Module Name: Ringier Axel Springer Bidder Adapter +Module Type: Bidder Adapter +Maintainer: support@ringpublishing.com +``` + +# Description + +Module that connects to Ringer Axel Springer demand sources. +Only banner format is supported. + +# Test Parameters +```js +var adUnits = [{ + code: 'test-div-ad', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bids: [{ + bidder: 'ras', + params: { + network: '4178463', + site: 'test', + area: 'areatest', + slot: 'slot' + } + }] +}]; +``` + +# Parameters + +| Name | Scope | Type | Description | Example +| --- | --- | --- | --- | --- +| network | required | String | Specific identifier provided by RAS | `"4178463"` +| site | required | String | Specific identifier name (case-insensitive) that is associated with this ad unit and provided by RAS | `"example_com"` +| area | required | String | Ad unit category name; only case-insensitive alphanumeric with underscores and hyphens are allowed | `"sport"` +| slot | required | String | Ad unit placement name (case-insensitive) provided by RAS | `"slot"` +| pageContext | optional | Object | Web page context data | `{}` +| pageContext.dr | optional | String | Document referrer URL address | `"https://example.com/"` +| pageContext.du | optional | String | Document URL address | `"https://example.com/sport/football/article.html?id=932016a5-02fc-4d5c-b643-fafc2f270f06"` +| pageContext.dv | optional | String | Document virtual address as slash-separated path that may consist of any number of parts (case-insensitive alphanumeric with underscores and hyphens); first part should be the same as `site` value and second as `area` value; next parts may reflect website navigation | `"example_com/sport/football"` +| pageContext.keyWords | optional | String[] | List of keywords associated with this ad unit; only case-insensitive alphanumeric with underscores and hyphens are allowed | `["euro", "lewandowski"]` +| pageContext.keyValues | optional | Object | Key-values associated with this ad unit (case-insensitive); following characters are not allowed in the values: `" ' = ! + # * ~ ; ^ ( ) < > [ ] & @` | `{}` +| pageContext.keyValues.ci | optional | String | Content unique identifier | `"932016a5-02fc-4d5c-b643-fafc2f270f06"` +| pageContext.keyValues.adunit | optional | String | Ad unit name | `"example_com/sport"` diff --git a/modules/shinezBidAdapter.js b/modules/shinezBidAdapter.js new file mode 100644 index 00000000000..923ecec7e4b --- /dev/null +++ b/modules/shinezBidAdapter.js @@ -0,0 +1,435 @@ +import { logWarn, logInfo, isArray, isFn, deepAccess, isEmpty, contains, timestamp, getBidIdParameter, triggerPixel, isInteger } from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {config} from '../src/config.js'; + +const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; +const BIDDER_CODE = 'shinez'; +const ADAPTER_VERSION = '1.0.0'; +const TTL = 360; +const CURRENCY = 'USD'; +const SELLER_ENDPOINT = 'https://hb.sweetgum.io/'; +const MODES = { + PRODUCTION: 'hb-sz-multi', + TEST: 'hb-multi-sz-test' +} +const SUPPORTED_SYNC_METHODS = { + IFRAME: 'iframe', + PIXEL: 'pixel' +} + +export const spec = { + code: BIDDER_CODE, + version: ADAPTER_VERSION, + supportedMediaTypes: SUPPORTED_AD_TYPES, + isBidRequestValid: function (bidRequest) { + if (!bidRequest.params) { + logWarn('no params have been set to Shinez adapter'); + return false; + } + + if (!bidRequest.params.org) { + logWarn('org is a mandatory param for Shinez adapter'); + return false; + } + + return true; + }, + buildRequests: function (validBidRequests, bidderRequest) { + const combinedRequestsObject = {}; + + // use data from the first bid, to create the general params for all bids + const generalObject = validBidRequests[0]; + const testMode = generalObject.params.testMode; + + combinedRequestsObject.params = generateGeneralParams(generalObject, bidderRequest); + combinedRequestsObject.bids = generateBidsParams(validBidRequests, bidderRequest); + + return { + method: 'POST', + url: getEndpoint(testMode), + data: combinedRequestsObject + } + }, + interpretResponse: function ({body}) { + const bidResponses = []; + + if (body.bids) { + body.bids.forEach(adUnit => { + const bidResponse = { + requestId: adUnit.requestId, + cpm: adUnit.cpm, + currency: adUnit.currency || CURRENCY, + width: adUnit.width, + height: adUnit.height, + ttl: adUnit.ttl || TTL, + creativeId: adUnit.requestId, + netRevenue: adUnit.netRevenue || true, + nurl: adUnit.nurl, + mediaType: adUnit.mediaType, + meta: { + mediaType: adUnit.mediaType + } + }; + + if (adUnit.mediaType === VIDEO) { + bidResponse.vastXml = adUnit.vastXml; + } else if (adUnit.mediaType === BANNER) { + bidResponse.ad = adUnit.ad; + } + + if (adUnit.adomain && adUnit.adomain.length) { + bidResponse.meta.advertiserDomains = adUnit.adomain; + } + + bidResponses.push(bidResponse); + }); + } + + return bidResponses; + }, + getUserSyncs: function (syncOptions, serverResponses) { + const syncs = []; + for (const response of serverResponses) { + if (syncOptions.iframeEnabled && response.body.params.userSyncURL) { + syncs.push({ + type: 'iframe', + url: response.body.params.userSyncURL + }); + } + if (syncOptions.pixelEnabled && isArray(response.body.params.userSyncPixels)) { + const pixels = response.body.params.userSyncPixels.map(pixel => { + return { + type: 'image', + url: pixel + } + }) + syncs.push(...pixels) + } + } + return syncs; + }, + onBidWon: function (bid) { + if (bid == null) { + return; + } + + logInfo('onBidWon:', bid); + if (bid.hasOwnProperty('nurl') && bid.nurl.length > 0) { + triggerPixel(bid.nurl); + } + } +}; + +registerBidder(spec); + +/** + * Get floor price + * @param bid {bid} + * @returns {Number} + */ +function getFloor(bid, mediaType) { + if (!isFn(bid.getFloor)) { + return 0; + } + let floorResult = bid.getFloor({ + currency: CURRENCY, + mediaType: mediaType, + size: '*' + }); + return floorResult.currency === CURRENCY && floorResult.floor ? floorResult.floor : 0; +} + +/** + * Get the the ad sizes array from the bid + * @param bid {bid} + * @returns {Array} + */ +function getSizesArray(bid, mediaType) { + let sizesArray = [] + + if (deepAccess(bid, `mediaTypes.${mediaType}.sizes`)) { + sizesArray = bid.mediaTypes[mediaType].sizes; + } else if (Array.isArray(bid.sizes) && bid.sizes.length > 0) { + sizesArray = bid.sizes; + } + + return sizesArray; +} + +/** + * Get schain string value + * @param schainObject {Object} + * @returns {string} + */ +function getSupplyChain(schainObject) { + if (isEmpty(schainObject)) { + return ''; + } + let scStr = `${schainObject.ver},${schainObject.complete}`; + schainObject.nodes.forEach((node) => { + scStr += '!'; + scStr += `${getEncodedValIfNotEmpty(node.asi)},`; + scStr += `${getEncodedValIfNotEmpty(node.sid)},`; + scStr += `${node.hp ? encodeURIComponent(node.hp) : ''},`; + scStr += `${getEncodedValIfNotEmpty(node.rid)},`; + scStr += `${getEncodedValIfNotEmpty(node.name)},`; + scStr += `${getEncodedValIfNotEmpty(node.domain)}`; + }); + return scStr; +} + +/** + * Get encoded node value + * @param val {string} + * @returns {string} + */ +function getEncodedValIfNotEmpty(val) { + return !isEmpty(val) ? encodeURIComponent(val) : ''; +} + +/** + * Get preferred user-sync method based on publisher configuration + * @param bidderCode {string} + * @returns {string} + */ +function getAllowedSyncMethod(filterSettings, bidderCode) { + const iframeConfigsToCheck = ['all', 'iframe']; + const pixelConfigToCheck = 'image'; + if (filterSettings && iframeConfigsToCheck.some(config => isSyncMethodAllowed(filterSettings[config], bidderCode))) { + return SUPPORTED_SYNC_METHODS.IFRAME; + } + if (!filterSettings || !filterSettings[pixelConfigToCheck] || isSyncMethodAllowed(filterSettings[pixelConfigToCheck], bidderCode)) { + return SUPPORTED_SYNC_METHODS.PIXEL; + } +} + +/** + * Check if sync rule is supported + * @param syncRule {Object} + * @param bidderCode {string} + * @returns {boolean} + */ +function isSyncMethodAllowed(syncRule, bidderCode) { + if (!syncRule) { + return false; + } + const isInclude = syncRule.filter === 'include'; + const bidders = isArray(syncRule.bidders) ? syncRule.bidders : [bidderCode]; + return isInclude && contains(bidders, bidderCode); +} + +/** + * Get the seller endpoint + * @param testMode {boolean} + * @returns {string} + */ +function getEndpoint(testMode) { + return testMode + ? SELLER_ENDPOINT + MODES.TEST + : SELLER_ENDPOINT + MODES.PRODUCTION; +} + +/** + * get device type + * @param uad {ua} + * @returns {string} + */ +function getDeviceType(ua) { + if (/ipad|android 3.0|xoom|sch-i800|playbook|tablet|kindle/i + .test(ua.toLowerCase())) { + return '5'; + } + if (/iphone|ipod|android|blackberry|opera|mini|windows\sce|palm|smartphone|iemobile/i + .test(ua.toLowerCase())) { + return '4'; + } + if (/smart[-_\s]?tv|hbbtv|appletv|googletv|hdmi|netcast|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b/i + .test(ua.toLowerCase())) { + return '3'; + } + return '1'; +} + +function generateBidsParams(validBidRequests, bidderRequest) { + const bidsArray = []; + + if (validBidRequests.length) { + validBidRequests.forEach(bid => { + bidsArray.push(generateBidParameters(bid, bidderRequest)); + }); + } + + return bidsArray; +} + +/** + * Generate bid specific parameters + * @param {bid} bid + * @param {bidderRequest} bidderRequest + * @returns {Object} bid specific params object + */ +function generateBidParameters(bid, bidderRequest) { + const {params} = bid; + const mediaType = isBanner(bid) ? BANNER : VIDEO; + const sizesArray = getSizesArray(bid, mediaType); + + // fix floor price in case of NAN + if (isNaN(params.floorPrice)) { + params.floorPrice = 0; + } + + const bidObject = { + mediaType, + adUnitCode: getBidIdParameter('adUnitCode', bid), + sizes: sizesArray, + floorPrice: Math.max(getFloor(bid, mediaType), params.floorPrice), + bidId: getBidIdParameter('bidId', bid), + bidderRequestId: getBidIdParameter('bidderRequestId', bid), + transactionId: getBidIdParameter('transactionId', bid), + }; + + const pos = deepAccess(bid, `mediaTypes.${mediaType}.pos`); + if (pos) { + bidObject.pos = pos; + } + + const gpid = deepAccess(bid, `ortb2Imp.ext.gpid`); + if (gpid) { + bidObject.gpid = gpid; + } + + const placementId = params.placementId || deepAccess(bid, `mediaTypes.${mediaType}.name`); + if (placementId) { + bidObject.placementId = placementId; + } + + if (mediaType === VIDEO) { + const playbackMethod = deepAccess(bid, `mediaTypes.video.playbackmethod`); + let playbackMethodValue; + + // verify playbackMethod is of type integer array, or integer only. + if (Array.isArray(playbackMethod) && isInteger(playbackMethod[0])) { + // only the first playbackMethod in the array will be used, according to OpenRTB 2.5 recommendation + playbackMethodValue = playbackMethod[0]; + } else if (isInteger(playbackMethod)) { + playbackMethodValue = playbackMethod; + } + + if (playbackMethodValue) { + bidObject.playbackMethod = playbackMethodValue; + } + + const placement = deepAccess(bid, `mediaTypes.video.placement`); + if (placement) { + bidObject.placement = placement; + } + + const minDuration = deepAccess(bid, `mediaTypes.video.minduration`); + if (minDuration) { + bidObject.minDuration = minDuration; + } + + const maxDuration = deepAccess(bid, `mediaTypes.video.maxduration`); + if (maxDuration) { + bidObject.maxDuration = maxDuration; + } + + const skip = deepAccess(bid, `mediaTypes.video.skip`); + if (skip) { + bidObject.skip = skip; + } + + const linearity = deepAccess(bid, `mediaTypes.video.linearity`); + if (linearity) { + bidObject.linearity = linearity; + } + } + + return bidObject; +} + +function isBanner(bid) { + return bid.mediaTypes && bid.mediaTypes.banner; +} + +/** + * Generate params that are common between all bids + * @param {single bid object} generalObject + * @param {bidderRequest} bidderRequest + * @returns {object} the common params object + */ +function generateGeneralParams(generalObject, bidderRequest) { + const domain = window.location.hostname; + const {syncEnabled, filterSettings} = config.getConfig('userSync') || {}; + const {bidderCode} = bidderRequest; + const generalBidParams = generalObject.params; + const timeout = config.getConfig('bidderTimeout'); + + // these params are snake_case instead of camelCase to allow backwards compatability on the server. + // in the future, these will be converted to camelCase to match our convention. + const generalParams = { + wrapper_type: 'prebidjs', + wrapper_vendor: '$$PREBID_GLOBAL$$', + wrapper_version: '$prebid.version$', + adapter_version: ADAPTER_VERSION, + auction_start: timestamp(), + publisher_id: generalBidParams.org, + publisher_name: domain, + site_domain: domain, + dnt: (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0, + device_type: getDeviceType(navigator.userAgent), + ua: navigator.userAgent, + session_id: getBidIdParameter('auctionId', generalObject), + tmax: timeout + } + + const userIdsParam = getBidIdParameter('userId', generalObject); + if (userIdsParam) { + generalParams.userIds = JSON.stringify(userIdsParam); + } + + const ortb2Metadata = bidderRequest.ortb2 || {}; + if (ortb2Metadata.site) { + generalParams.site_metadata = JSON.stringify(ortb2Metadata.site); + } + if (ortb2Metadata.user) { + generalParams.user_metadata = JSON.stringify(ortb2Metadata.user); + } + + if (syncEnabled) { + const allowedSyncMethod = getAllowedSyncMethod(filterSettings, bidderCode); + if (allowedSyncMethod) { + generalParams.cs_method = allowedSyncMethod; + } + } + + if (bidderRequest.uspConsent) { + generalParams.us_privacy = bidderRequest.uspConsent; + } + + if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) { + generalParams.gdpr = bidderRequest.gdprConsent.gdprApplies; + generalParams.gdpr_consent = bidderRequest.gdprConsent.consentString; + } + + if (generalBidParams.ifa) { + generalParams.ifa = generalBidParams.ifa; + } + + if (generalObject.schain) { + generalParams.schain = getSupplyChain(generalObject.schain); + } + + if (bidderRequest.ortb2 && bidderRequest.ortb2.site) { + generalParams.referrer = bidderRequest.ortb2.site.ref; + generalParams.page_url = bidderRequest.ortb2.site.page; + } + + if (bidderRequest && bidderRequest.refererInfo) { + generalParams.referrer = generalParams.referrer || deepAccess(bidderRequest, 'refererInfo.referer'); + generalParams.page_url = generalParams.page_url || config.getConfig('pageUrl') || deepAccess(window, 'location.href'); + } + + return generalParams +} diff --git a/test/spec/modules/1plusXRtdProvider_spec.js b/test/spec/modules/1plusXRtdProvider_spec.js new file mode 100644 index 00000000000..9682e4b62f8 --- /dev/null +++ b/test/spec/modules/1plusXRtdProvider_spec.js @@ -0,0 +1,430 @@ +import { config } from 'src/config'; +import { + onePlusXSubmodule, + segtaxes, + extractConfig, + buildOrtb2Updates, + updateBidderConfig, + setTargetingDataToConfig +} from 'modules/1plusXRtdProvider'; + +describe('1plusXRtdProvider', () => { + // Fake server config + let fakeServer; + const fakeResponseHeaders = { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*' + }; + const fakeResponse = { + s: ['segment1', 'segment2', 'segment3'], + t: ['targeting1', 'targeting2', 'targeting3'] + }; + + // Bid request config + const reqBidsConfigObj = { + adUnits: [{ + bids: [ + { bidder: 'appnexus' } + ] + }] + }; + + // Bidder configs + const bidderConfigInitial = { + ortb2: { + user: { keywords: '' }, + site: { ext: {} } + } + } + const bidderConfigInitialWith1plusXUserData = { + ortb2: { + user: { + data: [{ name: '1plusX.com', segment: [{ id: 'initial' }] }] + }, + site: { content: { data: [] } } + } + } + const bidderConfigInitialWithUserData = { + ortb2: { + user: { + data: [{ name: 'hello.world', segment: [{ id: 'initial' }] }] + }, + site: { content: { data: [] } } + } + } + const bidderConfigInitialWith1plusXSiteContent = { + ortb2: { + user: { data: [] }, + site: { + content: { + data: [{ + name: '1plusX.com', segment: [{ id: 'initial' }], ext: { segtax: 525 } + }] + } + }, + } + } + const bidderConfigInitialWithSiteContent = { + ortb2: { + user: { data: [] }, + site: { + content: { + data: [{ name: 'hello.world', segment: [{ id: 'initial' }] }] + } + }, + } + } + // Util functions + const randomBidder = (len = 5) => Math.random().toString(36).replace(/[^a-z]+/g, '').substring(0, len); + + before(() => { + config.resetConfig(); + }) + + after(() => { }) + + beforeEach(() => { + fakeServer = sinon.createFakeServer(); + fakeServer.respondWith('GET', '*', [200, fakeResponseHeaders, JSON.stringify(fakeResponse)]); + fakeServer.respondImmediately = true; + fakeServer.autoRespond = true; + }) + + describe('onePlusXSubmodule', () => { + it('init is successfull', () => { + const initResult = onePlusXSubmodule.init(); + expect(initResult).to.be.true; + }) + + it('callback is called after getBidRequestData', () => { + // Nice case; everything runs as expected + { + const callbackSpy = sinon.spy(); + const config = { params: { customerId: 'test', bidders: ['appnexus'] } }; + onePlusXSubmodule.getBidRequestData(reqBidsConfigObj, callbackSpy, config); + setTimeout(() => { + expect(callbackSpy.calledOnce).to.be.true + }, 100) + } + // No customer id in config => error but still callback called + { + const callbackSpy = sinon.spy(); + const config = {} + onePlusXSubmodule.getBidRequestData(reqBidsConfigObj, callbackSpy, config); + setTimeout(() => { + expect(callbackSpy.calledOnce).to.be.true + }, 100); + } + // No bidders in config => error but still callback called + { + const callbackSpy = sinon.spy(); + const config = { customerId: 'test' } + onePlusXSubmodule.getBidRequestData(reqBidsConfigObj, callbackSpy, config); + setTimeout(() => { + expect(callbackSpy.calledOnce).to.be.true + }, 100); + } + }) + }) + + describe('extractConfig', () => { + const customerId = 'test'; + const timeout = 1000; + const bidders = ['appnexus']; + + it('Throws an error if no customerId is specified', () => { + const moduleConfig = { params: { timeout, bidders } }; + expect(() => extractConfig(moduleConfig, reqBidsConfigObj)).to.throw(); + }) + it('Throws an error if no bidder is specified', () => { + const moduleConfig = { params: { customerId, timeout } }; + expect(() => extractConfig(moduleConfig, reqBidsConfigObj)).to.throw(); + }) + it("Throws an error if there's no bidder in reqBidsConfigObj", () => { + const moduleConfig = { params: { customerId, timeout, bidders } }; + const reqBidsConfigEmpty = { adUnits: [{ bids: [] }] }; + expect(() => extractConfig(moduleConfig, reqBidsConfigEmpty)).to.throw(); + }) + it('Returns an object containing the parameters specified', () => { + const moduleConfig = { params: { customerId, timeout, bidders } }; + const expectedKeys = ['customerId', 'timeout', 'bidders'] + const extractedConfig = extractConfig(moduleConfig, reqBidsConfigObj); + expect(extractedConfig).to.be.an('object').and.to.have.all.keys(expectedKeys); + expect(extractedConfig.customerId).to.equal(customerId); + expect(extractedConfig.timeout).to.equal(timeout); + expect(extractedConfig.bidders).to.deep.equal(bidders); + }) + /* 1plusX RTD module may only use bidders that are both specified in : + - the bid request configuration + - AND in the 1plusX RTD module configuration + Below 2 tests are enforcing those rules + */ + it('Returns the intersection of bidders found in bid request config & module config', () => { + const bidders = ['appnexus', 'rubicon']; + const moduleConfig = { params: { customerId, timeout, bidders } }; + const { bidders: extractedBidders } = extractConfig(moduleConfig, reqBidsConfigObj); + expect(extractedBidders).to.be.an('array').and.to.have.length(1); 7 + expect(extractedBidders[0]).to.equal('appnexus'); + }) + it('Throws an error if no bidder can be used by the module', () => { + const bidders = ['rubicon']; + const moduleConfig = { params: { customerId, timeout, bidders } }; + expect(() => extractConfig(moduleConfig, reqBidsConfigObj)).to.throw(); + }) + }) + + describe('buildOrtb2Updates', () => { + it('fills site.content.data & user.data in the ortb2 config', () => { + const rtdData = { segments: fakeResponse.s, topics: fakeResponse.t }; + const ortb2Updates = buildOrtb2Updates(rtdData, randomBidder()); + + const expectedOutput = { + siteContentData: { + name: '1plusX.com', + segment: rtdData.topics.map((topicId) => ({ id: topicId })), + ext: { segtax: segtaxes.CONTENT } + }, + userData: { + name: '1plusX.com', + segment: rtdData.segments.map((segmentId) => ({ id: segmentId })) + } + } + expect([ortb2Updates]).to.deep.include.members([expectedOutput]); + }); + it('fills site.keywords in the ortb2 config (appnexus specific)', () => { + const rtdData = { segments: fakeResponse.s, topics: fakeResponse.t }; + const ortb2Updates = buildOrtb2Updates(rtdData, 'appnexus'); + + const expectedOutput = { + site: { + keywords: rtdData.topics.join(','), + } + } + expect([ortb2Updates]).to.deep.include.members([expectedOutput]); + }); + + it('defaults to empty array if no segment is given', () => { + const rtdData = { topics: fakeResponse.t }; + const ortb2Updates = buildOrtb2Updates(rtdData, randomBidder()); + + const expectedOutput = { + siteContentData: { + name: '1plusX.com', + segment: rtdData.topics.map((topicId) => ({ id: topicId })), + ext: { segtax: segtaxes.CONTENT } + }, + userData: { + name: '1plusX.com', + segment: [] + } + } + expect(ortb2Updates).to.deep.include(expectedOutput); + }) + + it('defaults to empty array if no topic is given', () => { + const rtdData = { segments: fakeResponse.s }; + const ortb2Updates = buildOrtb2Updates(rtdData, randomBidder()); + + const expectedOutput = { + siteContentData: { + name: '1plusX.com', + segment: [], + ext: { segtax: segtaxes.CONTENT } + }, + userData: { + name: '1plusX.com', + segment: rtdData.segments.map((segmentId) => ({ id: segmentId })) + } + } + expect(ortb2Updates).to.deep.include(expectedOutput); + }) + it('defaults to empty string if no topic is given (appnexus specific)', () => { + const rtdData = { segments: fakeResponse.s }; + const ortb2Updates = buildOrtb2Updates(rtdData, 'appnexus'); + + const expectedOutput = { + site: { + keywords: '', + } + } + expect(ortb2Updates).to.deep.include(expectedOutput); + }) + }) + + describe('updateBidderConfig', () => { + const ortb2UpdatesAppNexus = { + site: { + keywords: fakeResponse.t.join(','), + }, + userData: { + name: '1plusX.com', + segment: fakeResponse.s.map((segmentId) => ({ id: segmentId })) + } + } + const ortb2Updates = { + siteContentData: { + name: '1plusX.com', + segment: fakeResponse.t.map((topicId) => ({ id: topicId })), + ext: { segtax: segtaxes.CONTENT } + }, + userData: { + name: '1plusX.com', + segment: fakeResponse.s.map((segmentId) => ({ id: segmentId })) + } + } + + it('merges fetched data in bidderConfig for configured bidders', () => { + const bidder = randomBidder(); + // Set initial config + config.setBidderConfig({ + bidders: [bidder], + config: bidderConfigInitial + }); + // Call submodule's setBidderConfig + const newBidderConfig = updateBidderConfig(bidder, ortb2Updates, config.getBidderConfig()); + // Check that the targeting data has been set in the config + expect(newBidderConfig).not.to.be.null; + expect(newBidderConfig.ortb2.site.content.data).to.deep.include(ortb2Updates.siteContentData); + expect(newBidderConfig.ortb2.user.data).to.deep.include(ortb2Updates.userData); + // Check that existing config didn't get erased + expect(newBidderConfig.ortb2.site).to.deep.include(bidderConfigInitial.ortb2.site); + expect(newBidderConfig.ortb2.user).to.deep.include(bidderConfigInitial.ortb2.user); + }) + + it('merges fetched data in bidderConfig for configured bidders (appnexus specific)', () => { + const bidder = 'appnexus'; + // Set initial config + config.setBidderConfig({ + bidders: [bidder], + config: bidderConfigInitial + }); + // Call submodule's setBidderConfig + const newBidderConfig = updateBidderConfig(bidder, ortb2UpdatesAppNexus, config.getBidderConfig()); + + // Check that the targeting data has been set in the config + expect(newBidderConfig).not.to.be.null; + expect(newBidderConfig.ortb2.site).to.deep.include(ortb2UpdatesAppNexus.site); + // Check that existing config didn't get erased + expect(newBidderConfig.ortb2.site).to.deep.include(bidderConfigInitial.ortb2.site); + expect(newBidderConfig.ortb2.user).to.deep.include(bidderConfigInitial.ortb2.user); + }) + + it('overwrites an existing 1plus.com entry in ortb2.user.data', () => { + const bidder = randomBidder(); + // Set initial config + config.setBidderConfig({ + bidders: [bidder], + config: bidderConfigInitialWith1plusXUserData + }); + // Save previous user.data entry + const previousUserData = bidderConfigInitialWith1plusXUserData.ortb2.user.data[0]; + // Call submodule's setBidderConfig + const newBidderConfig = updateBidderConfig(bidder, ortb2Updates, config.getBidderConfig()); + // Check that the targeting data has been set in the config + expect(newBidderConfig).not.to.be.null; + expect(newBidderConfig.ortb2.user.data).to.deep.include(ortb2Updates.userData); + expect(newBidderConfig.ortb2.user.data).not.to.include(previousUserData); + }) + it("doesn't overwrite entries in ortb2.user.data that aren't 1plusx.com", () => { + const bidder = randomBidder(); + // Set initial config + config.setBidderConfig({ + bidders: [bidder], + config: bidderConfigInitialWithUserData + }); + // Save previous user.data entry + const previousUserData = bidderConfigInitialWithUserData.ortb2.user.data[0]; + // Call submodule's setBidderConfig + const newBidderConfig = updateBidderConfig(bidder, ortb2Updates, config.getBidderConfig()); + // Check that the targeting data has been set in the config + expect(newBidderConfig).not.to.be.null; + expect(newBidderConfig.ortb2.user.data).to.deep.include(ortb2Updates.userData); + expect(newBidderConfig.ortb2.user.data).to.deep.include(previousUserData); + }) + + it('overwrites an existing 1plus.com entry in ortb2.site.content.data', () => { + const bidder = randomBidder(); + // Set initial config + config.setBidderConfig({ + bidders: [bidder], + config: bidderConfigInitialWith1plusXSiteContent + }); + // Save previous user.data entry + const previousSiteContent = bidderConfigInitialWith1plusXSiteContent.ortb2.site.content.data[0]; + // Call submodule's setBidderConfig + const newBidderConfig = updateBidderConfig(bidder, ortb2Updates, config.getBidderConfig()); + // Check that the targeting data has been set in the config + expect(newBidderConfig).not.to.be.null; + expect(newBidderConfig.ortb2.site.content.data).to.deep.include(ortb2Updates.siteContentData); + expect(newBidderConfig.ortb2.site.content.data).not.to.include(previousSiteContent); + }) + it("doesn't overwrite entries in ortb2.site.content.data that aren't 1plusx.com", () => { + const bidder = randomBidder(); + // Set initial config + config.setBidderConfig({ + bidders: [bidder], + config: bidderConfigInitialWithSiteContent + }); + // Save previous user.data entry + const previousSiteContent = bidderConfigInitialWithSiteContent.ortb2.site.content.data[0]; + // Call submodule's setBidderConfig + const newBidderConfig = updateBidderConfig(bidder, ortb2Updates, config.getBidderConfig()); + // Check that the targeting data has been set in the config + expect(newBidderConfig).not.to.be.null; + expect(newBidderConfig.ortb2.site.content.data).to.deep.include(ortb2Updates.siteContentData); + expect(newBidderConfig.ortb2.site.content.data).to.deep.include(previousSiteContent); + }) + }) + + describe('setTargetingDataToConfig', () => { + const expectedKeywords = fakeResponse.t.join(','); + const expectedSiteContentObj = { + data: [{ + name: '1plusX.com', + segment: fakeResponse.t.map((topicId) => ({ id: topicId })), + ext: { segtax: segtaxes.CONTENT } + }] + } + const expectedUserObj = { + data: [{ + name: '1plusX.com', + segment: fakeResponse.s.map((segmentId) => ({ id: segmentId })) + }] + } + const expectedOrtb2 = { + appnexus: { + site: { keywords: expectedKeywords } + }, + rubicon: { + site: { content: expectedSiteContentObj }, + user: expectedUserObj + } + } + + it('sets the config for the selected bidders', () => { + const bidders = ['appnexus', 'rubicon']; + // setting initial config for those bidders + config.setBidderConfig({ + bidders, + config: bidderConfigInitial + }) + // call setTargetingDataToConfig + setTargetingDataToConfig(fakeResponse, { bidders }); + + // Check that the targeting data has been set in both configs + for (const bidder of bidders) { + const newConfig = config.getBidderConfig()[bidder]; + // Check that we got what we expect + const expectedConfErr = (prop) => `New config for ${bidder} doesn't comply with expected at ${prop}`; + expect(newConfig.ortb2.site, expectedConfErr('site')).to.deep.include(expectedOrtb2[bidder].site); + if (expectedOrtb2[bidder].user) { + expect(newConfig.ortb2.user, expectedConfErr('user')).to.deep.include(expectedOrtb2[bidder].user); + } + // Check that existing config didn't get erased + const existingConfErr = (prop) => `Existing config for ${bidder} got unlawfully overwritten at ${prop}`; + expect(newConfig.ortb2.site, existingConfErr('site')).to.deep.include(bidderConfigInitial.ortb2.site); + expect(newConfig.ortb2.user, existingConfErr('user')).to.deep.include(bidderConfigInitial.ortb2.user); + } + }) + }) +}) diff --git a/test/spec/modules/kargoAnalyticsAdapter_spec.js b/test/spec/modules/kargoAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..c27c8499aa1 --- /dev/null +++ b/test/spec/modules/kargoAnalyticsAdapter_spec.js @@ -0,0 +1,42 @@ +import kargoAnalyticsAdapter from 'modules/kargoAnalyticsAdapter.js'; +import { expect } from 'chai'; +import { server } from 'test/mocks/xhr.js'; +let events = require('src/events'); +let constants = require('src/constants.json'); + +describe('Kargo Analytics Adapter', function () { + const adapterConfig = { + provider: 'kargoAnalytics', + }; + + after(function () { + kargoAnalyticsAdapter.disableAnalytics(); + }); + + describe('main test flow', function () { + beforeEach(function () { + kargoAnalyticsAdapter.enableAnalytics(adapterConfig); + sinon.stub(events, 'getEvents').returns([]); + }); + + afterEach(function () { + events.getEvents.restore(); + }); + + it('bid response data should send one request with auction ID, auction timeout, and response time', function() { + const bidResponse = { + bidder: 'kargo', + auctionId: '66529d4c-8998-47c2-ab3e-5b953490b98f', + timeToRespond: 192, + }; + + events.emit(constants.EVENTS.AUCTION_INIT, { + timeout: 1000 + }); + events.emit(constants.EVENTS.BID_RESPONSE, bidResponse); + + expect(server.requests.length).to.equal(1); + expect(server.requests[0].url).to.equal('https://krk.kargo.com/api/v1/event/auction-data?aid=66529d4c-8998-47c2-ab3e-5b953490b98f&ato=1000&rt=192&it=0'); + }); + }); +}); diff --git a/test/spec/modules/newspassidBidAdapter_spec.js b/test/spec/modules/newspassidBidAdapter_spec.js new file mode 100644 index 00000000000..bec6eea7bf2 --- /dev/null +++ b/test/spec/modules/newspassidBidAdapter_spec.js @@ -0,0 +1,1793 @@ +import { expect } from 'chai'; +import { spec, defaultSize } from 'modules/newspassidBidAdapter.js'; +import { config } from 'src/config.js'; +import {getGranularityKeyName, getGranularityObject} from '../../../modules/newspassidBidAdapter.js'; +import * as utils from '../../../src/utils.js'; +const NEWSPASSURI = 'https://bidder.newspassid.com/openrtb2/auction'; +const BIDDER_CODE = 'newspassid'; +var validBidRequests = [ + { + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', + bidId: '2899ec066a91ff8', + bidRequestsCount: 1, + bidder: 'newspassid', + bidderRequestId: '1c1586b27a1b5c8', + crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, + params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, banner: { format: [{ w: 300, h: 250 }, { w: 300, h: 600 }], h: 250, topframe: 1, w: 300 } } ] }, + sizes: [[300, 250], [300, 600]], + transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' + } +]; +var validBidRequestsNoCustomData = [ + { + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', + bidId: '2899ec066a91ff8', + bidRequestsCount: 1, + bidder: 'newspassid', + bidderRequestId: '1c1586b27a1b5c8', + crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, + params: { publisherId: '9876abcd12-3', placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, banner: { format: [{ w: 300, h: 250 }, { w: 300, h: 600 }], h: 250, topframe: 1, w: 300 } } ] }, + sizes: [[300, 250], [300, 600]], + transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' + } +]; +var validBidRequestsMulti = [ + { + testId: 1, + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', + bidId: '2899ec066a91ff8', + bidRequestsCount: 1, + bidder: 'newspassid', + bidderRequestId: '1c1586b27a1b5c8', + crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, + params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, banner: { format: [{ w: 300, h: 250 }, { w: 300, h: 600 }], h: 250, topframe: 1, w: 300 } } ] }, + sizes: [[300, 250], [300, 600]], + transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' + }, + { + testId: 2, + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', + bidId: '2899ec066a91ff0', + bidRequestsCount: 1, + bidder: 'newspassid', + bidderRequestId: '1c1586b27a1b5c0', + crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, + params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, banner: { format: [{ w: 300, h: 250 }, { w: 300, h: 600 }], h: 250, topframe: 1, w: 300 } } ] }, + sizes: [[300, 250], [300, 600]], + transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' + } +]; +var validBidRequestsWithUserIdData = [ + { + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', + bidId: '2899ec066a91ff8', + bidRequestsCount: 1, + bidder: 'newspassid', + bidderRequestId: '1c1586b27a1b5c8', + crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, + params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, banner: { format: [{ w: 300, h: 250 }, { w: 300, h: 600 }], h: 250, topframe: 1, w: 300 } } ] }, + sizes: [[300, 250], [300, 600]], + transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87', + userId: { + 'pubcid': '12345678', + 'tdid': '1111tdid', + 'id5id': { uid: '1111', ext: { linkType: 2, abTestingControlGroup: false } }, + 'criteoId': '1111criteoId', + 'idl_env': 'liverampId', + 'lipb': {'lipbid': 'lipbidId123'}, + 'parrableId': {'eid': '01.5678.parrableid'}, + 'sharedid': {'id': '01EAJWWNEPN3CYMM5N8M5VXY22', 'third': '01EAJWWNEPN3CYMM5N8M5VXY22'} + }, + userIdAsEids: [ + { + 'source': 'pubcid.org', + 'uids': [ + { + 'id': '12345678', + 'atype': 1 + } + ] + }, + { + 'source': 'adserver.org', + 'uids': [{ + 'id': '1111tdid', + 'atype': 1, + 'ext': { + 'rtiPartner': 'TDID' + } + }] + }, + { + 'source': 'id5-sync.com', + 'uids': [{ + 'id': 'ID5-someId', + 'atype': 1, + }] + }, + { + 'source': 'criteoId', + 'uids': [{ + 'id': '1111criteoId', + 'atype': 1, + }] + }, + { + 'source': 'idl_env', + 'uids': [{ + 'id': 'liverampId', + 'atype': 1, + }] + }, + { + 'source': 'lipb', + 'uids': [{ + 'id': {'lipbid': 'lipbidId123'}, + 'atype': 1, + }] + }, + { + 'source': 'parrableId', + 'uids': [{ + 'id': {'eid': '01.5678.parrableid'}, + 'atype': 1, + }] + } + ] + } +]; +var validBidRequestsMinimal = [ + { + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', + bidId: '2899ec066a91ff8', + bidRequestsCount: 1, + bidder: 'newspassid', + bidderRequestId: '1c1586b27a1b5c8', + params: { publisherId: '9876abcd12-3', placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, banner: { format: [{ w: 300, h: 250 }, { w: 300, h: 600 }], h: 250, topframe: 1, w: 300 } } ] }, + sizes: [[300, 250], [300, 600]], + transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' + } +]; +var validBidRequestsNoSizes = [ + { + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', + bidId: '2899ec066a91ff8', + bidRequestsCount: 1, + bidder: 'newspassid', + bidderRequestId: '1c1586b27a1b5c8', + crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, + params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, banner: { format: [{ w: 300, h: 250 }, { w: 300, h: 600 }], h: 250, topframe: 1, w: 300 } } ] }, + transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' + } +]; +var validBidRequestsWithBannerMediaType = [ + { + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', + bidId: '2899ec066a91ff8', + bidRequestsCount: 1, + bidder: 'newspassid', + bidderRequestId: '1c1586b27a1b5c8', + crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, + params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, banner: { format: [{ w: 300, h: 250 }, { w: 300, h: 600 }], h: 250, topframe: 1, w: 300 } } ] }, + mediaTypes: {banner: {sizes: [[300, 250], [300, 600]]}}, + transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' + } +]; +var validBidRequestsIsThisCamelCaseEnough = [ + { + 'bidder': 'newspassid', + 'testname': 'validBidRequestsIsThisCamelCaseEnough', + 'params': { + 'publisherId': 'newspassRUP0001', + 'placementId': '8000000009', + 'siteId': '4204204201', + 'customData': [ + { + 'settings': {}, + 'targeting': { + 'sens': 'f', + 'pt1': '/uk', + 'pt2': 'uk', + 'pt3': 'network-front', + 'pt4': 'ng', + 'pt5': [ + 'uk' + ], + 'pt7': 'desktop', + 'pt8': [ + 'tfmqxwj7q', + 'penl4dfdk', + 'sek9ghqwi' + ], + 'pt9': '|k0xw2vqzp33kklb3j5w4|||' + } + } + ], + 'userId': { + 'pubcid': '2ada6ae6-aeca-4e07-8922-a99b3aaf8a56' + }, + 'userIdAsEids': [ + { + 'source': 'pubcid.org', + 'uids': [ + { + 'id': '2ada6ae6-aeca-4e07-8922-a99b3aaf8a56', + 'atype': 1 + } + ] + } + ] + }, + mediaTypes: {banner: {sizes: [[300, 250], [300, 600]]}}, + 'adUnitCode': 'some-ad', + 'transactionId': '02c1ea7d-0bf2-451b-a122-1420040d1cf8', + 'bidId': '2899ec066a91ff8', + 'bidderRequestId': '1c1586b27a1b5c8', + 'auctionId': '0456c9b7-5ab2-4fec-9e10-f418d3d1f04c', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + } +]; +var validBidderRequest = { + auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', + auctionStart: 1536838908986, + bidderCode: 'newspassid', + bidderRequestId: '1c1586b27a1b5c8', + bids: [{ + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', + bidId: '2899ec066a91ff8', + bidRequestsCount: 1, + bidder: 'newspassid', + bidderRequestId: '1c1586b27a1b5c8', + crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, + params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { banner: { topframe: 1, w: 300, h: 250, format: [{ w: 300, h: 250 }, { w: 300, h: 600 }] }, id: '2899ec066a91ff8', secure: 1, tagid: 'undefined' } ] }, + sizes: [[300, 250], [300, 600]], + transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' + }], + doneCbCallCount: 1, + start: 1536838908987, + timeout: 3000 +}; +var emptyObject = {}; +var validResponse = { + 'body': { + 'id': 'd6198807-7a53-4141-b2db-d2cb754d68ba', + 'seatbid': [ + { + 'bid': [ + { + 'id': '677903815252395017', + 'impid': '2899ec066a91ff8', + 'price': 0.5, + 'adm': '', + 'adid': '98493581', + 'adomain': [ + 'http://prebid.org' + ], + 'iurl': 'https://fra1-ib.adnxs.com/cr?id=98493581', + 'cid': '9325', + 'crid': '98493581', + 'cat': [ + 'IAB3-1' + ], + 'w': 300, + 'h': 600, + 'ext': { + 'prebid': { + 'type': 'banner' + }, + 'bidder': { + 'appnexus': { + 'brand_id': 555545, + 'auction_id': 6500448734132353000, + 'bidder_id': 2, + 'bid_ad_type': 0 + } + } + } + } + ], + 'seat': 'appnexus' + } + ], + 'cur': 'GBP', /* NOTE - this is where cur is, not in the seatbids. */ + 'ext': { + 'responsetimemillis': { + 'appnexus': 47, + 'openx': 30 + } + }, + 'timing': { + 'start': 1536848078.089177, + 'end': 1536848078.142203, + 'TimeTaken': 0.05302619934082031 + } + }, + 'headers': {} +}; +var validResponse2BidsSameAdunit = { + 'body': { + 'id': 'd6198807-7a53-4141-b2db-d2cb754d68ba', + 'seatbid': [ + { + 'bid': [ + { + 'id': '677903815252395017', + 'impid': '2899ec066a91ff8', + 'price': 0.5, + 'adm': '', + 'adid': '98493581', + 'adomain': [ + 'http://prebid.org' + ], + 'iurl': 'https://fra1-ib.adnxs.com/cr?id=98493581', + 'cid': '9325', + 'crid': '98493581', + 'cat': [ + 'IAB3-1' + ], + 'w': 300, + 'h': 600, + 'ext': { + 'prebid': { + 'type': 'banner' + }, + 'bidder': { + 'appnexus': { + 'brand_id': 555545, + 'auction_id': 6500448734132353000, + 'bidder_id': 2, + 'bid_ad_type': 0 + } + } + } + }, + { + 'id': '677903815252395010', + 'impid': '2899ec066a91ff8', + 'price': 0.9, + 'adm': '', + 'adid': '98493580', + 'adomain': [ + 'http://prebid.org' + ], + 'iurl': 'https://fra1-ib.adnxs.com/cr?id=98493581', + 'cid': '9320', + 'crid': '98493580', + 'cat': [ + 'IAB3-1' + ], + 'w': 300, + 'h': 250, + 'ext': { + 'prebid': { + 'type': 'banner' + }, + 'bidder': { + 'appnexus': { + 'brand_id': 555540, + 'auction_id': 6500448734132353000, + 'bidder_id': 2, + 'bid_ad_type': 0 + } + } + } + } ], + 'seat': 'npappnexus' + } + ], + 'cur': 'GBP', /* NOTE - this is where cur is, not in the seatbids. */ + 'ext': { + 'responsetimemillis': { + 'appnexus': 47, + 'openx': 30 + } + }, + 'timing': { + 'start': 1536848078.089177, + 'end': 1536848078.142203, + 'TimeTaken': 0.05302619934082031 + } + }, + 'headers': {} +}; +var validBidResponse1adWith2Bidders = { + 'body': { + 'id': '91221f96-b931-4acc-8f05-c2a1186fa5ac', + 'seatbid': [ + { + 'bid': [ + { + 'id': 'd6198807-7a53-4141-b2db-d2cb754d68ba', + 'impid': '2899ec066a91ff8', + 'price': 0.36754, + 'adm': '', + 'adid': '134928661', + 'adomain': [ + 'somecompany.com' + ], + 'iurl': 'https:\/\/ams1-ib.adnxs.com\/cr?id=134928661', + 'cid': '8825', + 'crid': '134928661', + 'cat': [ + 'IAB8-15', + 'IAB8-16', + 'IAB8-4', + 'IAB8-1', + 'IAB8-14', + 'IAB8-6', + 'IAB8-13', + 'IAB8-3', + 'IAB8-17', + 'IAB8-12', + 'IAB8-8', + 'IAB8-7', + 'IAB8-2', + 'IAB8-9', + 'IAB8', + 'IAB8-11' + ], + 'w': 300, + 'h': 250, + 'ext': { + 'prebid': { + 'type': 'banner' + }, + 'bidder': { + 'appnexus': { + 'brand_id': 14640, + 'auction_id': 1.8369641905139e+18, + 'bidder_id': 2, + 'bid_ad_type': 0 + } + } + } + } + ], + 'seat': 'appnexus' + }, + { + 'bid': [ + { + 'id': '75665207-a1ca-49db-ba0e-a5e9c7d26f32', + 'impid': '37fff511779365a', + 'price': 1.046, + 'adm': '
removed
', + 'adomain': [ + 'kx.com' + ], + 'crid': '13005', + 'w': 300, + 'h': 250, + 'ext': { + 'prebid': { + 'type': 'banner' + } + } + } + ], + 'seat': 'openx' + } + ], + 'ext': { + 'responsetimemillis': { + 'appnexus': 91, + 'openx': 109, + 'npappnexus': 46, + 'npbeeswax': 2, + 'pangaea': 91 + } + } + }, + 'headers': {} +}; +var multiRequest1 = [ + { + 'bidder': 'newspassid', + 'params': { + 'publisherId': 'newspassRUP0001', + 'siteId': '4204204201', + 'placementId': '0420420421', + 'customData': [ + { + 'settings': {}, + 'targeting': { + 'sens': 'f', + 'pt1': '/uk', + 'pt2': 'uk', + 'pt3': 'network-front', + 'pt4': 'ng', + 'pt5': [ + 'uk' + ], + 'pt7': 'desktop', + 'pt8': [ + 'tfmqxwj7q', + 'penl4dfdk', + 'uayf5jmv3', + 't8nyiude5', + 'sek9ghqwi' + ], + 'pt9': '|k0xw2vqzp33kklb3j5w4|||' + } + } + ] + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ] + } + }, + 'adUnitCode': 'mpu', + 'transactionId': '6480bac7-31b5-4723-9145-ad8966660651', + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + 'bidId': '2d30e86db743a8', + 'bidderRequestId': '1d03a1dfc563fc', + 'auctionId': '592ee33b-fb2e-4c00-b2d5-383e99cac57f', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + }, + { + 'bidder': 'newspassid', + 'params': { + 'publisherId': 'newspassRUP0001', + 'siteId': '4204204201', + 'placementId': '0420420421', + 'customData': [ + { + 'settings': {}, + 'targeting': { + 'sens': 'f', + 'pt1': '/uk', + 'pt2': 'uk', + 'pt3': 'network-front', + 'pt4': 'ng', + 'pt5': [ + 'uk' + ], + 'pt7': 'desktop', + 'pt8': [ + 'tfmqxwj7q', + 'penl4dfdk', + 't8nxz6qzd', + 't8nyiude5', + 'sek9ghqwi' + ], + 'pt9': '|k0xw2vqzp33kklb3j5w4|||' + } + } + ] + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 728, + 90 + ], + [ + 970, + 250 + ] + ] + } + }, + 'adUnitCode': 'leaderboard', + 'transactionId': 'a49988e6-ae7c-46c4-9598-f18db49892a0', + 'sizes': [ + [ + 728, + 90 + ], + [ + 970, + 250 + ] + ], + 'bidId': '3025f169863b7f8', + 'bidderRequestId': '1d03a1dfc563fc', + 'auctionId': '592ee33b-fb2e-4c00-b2d5-383e99cac57f', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + } +]; +var multiBidderRequest1 = { + bidderRequest: { + 'bidderCode': 'newspassid', + 'auctionId': '592ee33b-fb2e-4c00-b2d5-383e99cac57f', + 'bidderRequestId': '1d03a1dfc563fc', + 'bids': [ + { + 'bidder': 'newspassid', + 'params': { + 'publisherId': 'newspassRUP0001', + 'siteId': '4204204201', + 'placementId': '0420420421', + 'customData': [ + { + 'settings': {}, + 'targeting': { + 'sens': 'f', + 'pt1': '/uk', + 'pt2': 'uk', + 'pt3': 'network-front', + 'pt4': 'ng', + 'pt5': [ + 'uk' + ], + 'pt7': 'desktop', + 'pt8': [ + 'tfmqxwj7q', + 'txeh7uyo0', + 't8nxz6qzd', + 't8nyiude5', + 'sek9ghqwi' + ], + 'pt9': '|k0xw2vqzp33kklb3j5w4|||' + } + } + ] + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ] + } + }, + 'adUnitCode': 'mpu', + 'transactionId': '6480bac7-31b5-4723-9145-ad8966660651', + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + 'bidId': '2d30e86db743a8', + 'bidderRequestId': '1d03a1dfc563fc', + 'auctionId': '592ee33b-fb2e-4c00-b2d5-383e99cac57f', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + }, + { + 'bidder': 'newspassid', + 'params': { + 'publisherId': 'newspassRUP0001', + 'siteId': '4204204201', + 'placementId': '0420420421', + 'customData': [ + { + 'settings': {}, + 'targeting': { + 'sens': 'f', + 'pt1': '/uk', + 'pt2': 'uk', + 'pt3': 'network-front', + 'pt4': 'ng', + 'pt5': [ + 'uk' + ], + 'pt7': 'desktop', + 'pt8': [ + 'tfmqxwj7q', + 'penl4dfdk', + 't8nxz6qzd', + 't8nyiude5', + 'sek9ghqwi' + ], + 'pt9': '|k0xw2vqzp33kklb3j5w4|||' + } + } + ] + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 728, + 90 + ], + [ + 970, + 250 + ] + ] + } + }, + 'adUnitCode': 'leaderboard', + 'transactionId': 'a49988e6-ae7c-46c4-9598-f18db49892a0', + 'sizes': [ + [ + 728, + 90 + ], + [ + 970, + 250 + ] + ], + 'bidId': '3025f169863b7f8', + 'bidderRequestId': '1d03a1dfc563fc', + 'auctionId': '592ee33b-fb2e-4c00-b2d5-383e99cac57f', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + } + ], + 'auctionStart': 1592918645574, + 'timeout': 3000, + 'refererInfo': { + 'referer': 'http://some.referrer.com', + 'reachedTop': true, + 'numIframes': 0, + 'stack': [ + 'http://some.referrer.com' + ] + }, + 'start': 1592918645578 + } +}; +var multiResponse1 = { + 'body': { + 'id': '592ee33b-fb2e-4c00-b2d5-383e99cac57f', + 'seatbid': [ + { + 'bid': [ + { + 'id': '4419718600113204943', + 'impid': '2d30e86db743a8', + 'price': 0.2484, + 'adm': '', + 'adid': '119683582', + 'adomain': [ + 'https://someurl.com' + ], + 'iurl': 'https://ams1-ib.adnxs.com/cr?id=119683582', + 'cid': '9979', + 'crid': '119683582', + 'cat': [ + 'IAB3' + ], + 'w': 300, + 'h': 250, + 'ext': { + 'prebid': { + 'type': 'banner' + }, + 'bidder': { + 'newspassid': {}, + 'appnexus': { + 'brand_id': 734921, + 'auction_id': 2995348111857539600, + 'bidder_id': 2, + 'bid_ad_type': 0 + } + } + }, + 'cpm': 0.2484, + 'bidId': '2d30e86db743a8', + 'requestId': '2d30e86db743a8', + 'width': 300, + 'height': 250, + 'ad': '', + 'netRevenue': true, + 'creativeId': '119683582', + 'currency': 'USD', + 'ttl': 300, + 'originalCpm': 0.2484, + 'originalCurrency': 'USD' + }, + { + 'id': '18552976939844681', + 'impid': '3025f169863b7f8', + 'price': 0.0621, + 'adm': '', + 'adid': '120179216', + 'adomain': [ + 'appnexus.com' + ], + 'iurl': 'https://ams1-ib.adnxs.com/cr?id=120179216', + 'cid': '9979', + 'crid': '120179216', + 'w': 970, + 'h': 250, + 'ext': { + 'prebid': { + 'type': 'banner' + }, + 'bidder': { + 'newspassid': {}, + 'appnexus': { + 'brand_id': 1, + 'auction_id': 3449036134472542700, + 'bidder_id': 2, + 'bid_ad_type': 0 + } + } + }, + 'cpm': 0.0621, + 'bidId': '3025f169863b7f8', + 'requestId': '3025f169863b7f8', + 'width': 970, + 'height': 250, + 'ad': '', + 'netRevenue': true, + 'creativeId': '120179216', + 'currency': 'USD', + 'ttl': 300, + 'originalCpm': 0.0621, + 'originalCurrency': 'USD' + }, + { + 'id': '18552976939844999', + 'impid': '3025f169863b7f8', + 'price': 0.521, + 'adm': '', + 'adid': '120179216', + 'adomain': [ + 'appnexus.com' + ], + 'iurl': 'https://ams1-ib.adnxs.com/cr?id=120179216', + 'cid': '9999', + 'crid': '120179299', + 'w': 728, + 'h': 90, + 'ext': { + 'prebid': { + 'type': 'banner' + }, + 'bidder': { + 'newspassid': {}, + 'appnexus': { + 'brand_id': 1, + 'auction_id': 3449036134472542700, + 'bidder_id': 2, + 'bid_ad_type': 0 + } + } + }, + 'cpm': 0.521, + 'bidId': '3025f169863b7f8', + 'requestId': '3025f169863b7f8', + 'width': 728, + 'height': 90, + 'ad': '', + 'netRevenue': true, + 'creativeId': '120179299', + 'currency': 'USD', + 'ttl': 300, + 'originalCpm': 0.0621, + 'originalCurrency': 'USD' + } + ], + 'seat': 'npappnexus' + }, + { + 'bid': [ + { + 'id': '1c605e8a-4992-4ec6-8a5c-f82e2938c2db', + 'impid': '2d30e86db743a8', + 'price': 0.01, + 'adm': '
', + 'crid': '540463358', + 'w': 300, + 'h': 250, + 'ext': { + 'prebid': { + 'type': 'banner' + }, + 'bidder': { + 'newspassid': {} + } + }, + 'cpm': 0.01, + 'bidId': '2d30e86db743a8', + 'requestId': '2d30e86db743a8', + 'width': 300, + 'height': 250, + 'ad': '
', + 'netRevenue': true, + 'creativeId': '540463358', + 'currency': 'USD', + 'ttl': 300, + 'originalCpm': 0.01, + 'originalCurrency': 'USD' + }, + { + 'id': '3edeb4f7-d91d-44e2-8aeb-4a2f6d295ce5', + 'impid': '3025f169863b7f8', + 'price': 0.01, + 'adm': '
', + 'crid': '540221061', + 'w': 970, + 'h': 250, + 'ext': { + 'prebid': { + 'type': 'banner' + }, + 'bidder': { + 'newspassid': {} + } + }, + 'cpm': 0.01, + 'bidId': '3025f169863b7f8', + 'requestId': '3025f169863b7f8', + 'width': 970, + 'height': 250, + 'ad': '
', + 'netRevenue': true, + 'creativeId': '540221061', + 'currency': 'USD', + 'ttl': 300, + 'originalCpm': 0.01, + 'originalCurrency': 'USD' + } + ], + 'seat': 'openx' + } + ], + 'ext': { + 'debug': {}, + 'responsetimemillis': { + 'beeswax': 6, + 'openx': 91, + 'npappnexus': 40, + 'npbeeswax': 6 + } + } + }, + 'headers': {} +}; +describe('newspassid Adapter', function () { + describe('isBidRequestValid', function () { + let validBidReq = { + bidder: BIDDER_CODE, + params: { + placementId: '1310000099', + publisherId: '9876abcd12-3', + siteId: '1234567890' + } + }; + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(validBidReq)).to.equal(true); + }); + var validBidReq2 = { + bidder: BIDDER_CODE, + params: { + placementId: '1310000099', + publisherId: '9876abcd12-3', + siteId: '1234567890', + customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}] + }, + siteId: 1234567890 + } + it('should return true when required params found and all optional params are valid', function () { + expect(spec.isBidRequestValid(validBidReq2)).to.equal(true); + }); + var xEmptyPlacement = { + bidder: BIDDER_CODE, + params: { + placementId: '', + publisherId: '9876abcd12-3', + siteId: '1234567890' + } + }; + it('should not validate empty placementId', function () { + expect(spec.isBidRequestValid(xEmptyPlacement)).to.equal(false); + }); + var xMissingPlacement = { + bidder: BIDDER_CODE, + params: { + publisherId: '9876abcd12-3', + siteId: '1234567890' + } + }; + it('should not validate missing placementId', function () { + expect(spec.isBidRequestValid(xMissingPlacement)).to.equal(false); + }); + var xBadPlacement = { + bidder: BIDDER_CODE, + params: { + placementId: '123X45', + publisherId: '9876abcd12-3', + siteId: '1234567890' + } + }; + it('should not validate placementId with a non-numeric value', function () { + expect(spec.isBidRequestValid(xBadPlacement)).to.equal(false); + }); + var xBadPlacementTooShort = { + bidder: BIDDER_CODE, + params: { + placementId: 123456789, /* should be exactly 10 chars */ + publisherId: '9876abcd12-3', + siteId: '1234567890' + } + }; + it('should not validate placementId with a numeric value of wrong length', function () { + expect(spec.isBidRequestValid(xBadPlacementTooShort)).to.equal(false); + }); + var xBadPlacementTooLong = { + bidder: BIDDER_CODE, + params: { + placementId: 12345678901, /* should be exactly 10 chars */ + publisherId: '9876abcd12-3', + siteId: '1234567890' + } + }; + it('should not validate placementId with a numeric value of wrong length', function () { + expect(spec.isBidRequestValid(xBadPlacementTooLong)).to.equal(false); + }); + var xMissingPublisher = { + bidder: BIDDER_CODE, + params: { + placementId: '1234567890', + siteId: '1234567890' + } + }; + it('should not validate missing publisherId', function () { + expect(spec.isBidRequestValid(xMissingPublisher)).to.equal(false); + }); + var xMissingSiteId = { + bidder: BIDDER_CODE, + params: { + publisherId: '9876abcd12-3', + placementId: '1234567890', + } + }; + it('should not validate missing sitetId', function () { + expect(spec.isBidRequestValid(xMissingSiteId)).to.equal(false); + }); + var xBadPublisherTooShort = { + bidder: BIDDER_CODE, + params: { + placementId: '1234567890', + publisherId: '9876abcd12a', + siteId: '1234567890' + } + }; + it('should not validate publisherId being too short', function () { + expect(spec.isBidRequestValid(xBadPublisherTooShort)).to.equal(false); + }); + var xBadPublisherTooLong = { + bidder: BIDDER_CODE, + params: { + placementId: '1234567890', + publisherId: '9876abcd12abc', + siteId: '1234567890' + } + }; + it('should not validate publisherId being too long', function () { + expect(spec.isBidRequestValid(xBadPublisherTooLong)).to.equal(false); + }); + var publisherNumericOk = { + bidder: BIDDER_CODE, + params: { + placementId: '1234567890', + publisherId: 123456789012, + siteId: '1234567890' + } + }; + it('should validate publisherId being 12 digits', function () { + expect(spec.isBidRequestValid(publisherNumericOk)).to.equal(true); + }); + var xEmptyPublisher = { + bidder: BIDDER_CODE, + params: { + placementId: '1234567890', + publisherId: '', + siteId: '1234567890' + } + }; + it('should not validate empty publisherId', function () { + expect(spec.isBidRequestValid(xEmptyPublisher)).to.equal(false); + }); + var xBadSite = { + bidder: BIDDER_CODE, + params: { + placementId: '1234567890', + publisherId: '9876abcd12-3', + siteId: '12345Z' + } + }; + it('should not validate bad siteId', function () { + expect(spec.isBidRequestValid(xBadSite)).to.equal(false); + }); + it('should not validate siteId too long', function () { + expect(spec.isBidRequestValid(xBadSite)).to.equal(false); + }); + it('should not validate siteId too short', function () { + expect(spec.isBidRequestValid(xBadSite)).to.equal(false); + }); + var allNonStrings = { + bidder: BIDDER_CODE, + params: { + placementId: 1234567890, + publisherId: '9876abcd12-3', + siteId: 1234567890 + } + }; + it('should validate all numeric values being sent as non-string numbers', function () { + expect(spec.isBidRequestValid(allNonStrings)).to.equal(true); + }); + var emptySiteId = { + bidder: BIDDER_CODE, + params: { + placementId: 1234567890, + publisherId: '9876abcd12-3', + siteId: '' + } + }; + it('should not validate siteId being empty string (it is required now)', function () { + expect(spec.isBidRequestValid(emptySiteId)).to.equal(false); + }); + var xBadCustomData = { + bidder: BIDDER_CODE, + params: { + 'placementId': '1234567890', + 'publisherId': '9876abcd12-3', + 'siteId': '1234567890', + 'customData': 'this aint gonna work' + } + }; + it('should not validate customData not being an array', function () { + expect(spec.isBidRequestValid(xBadCustomData)).to.equal(false); + }); + var xBadCustomDataOldCustomdataValue = { + bidder: BIDDER_CODE, + params: { + 'placementId': '1234567890', + 'publisherId': '9876abcd12-3', + 'siteId': '1234567890', + 'customData': {'gender': 'bart', 'age': 'low'} + } + }; + it('should not validate customData being an object, not an array', function () { + expect(spec.isBidRequestValid(xBadCustomDataOldCustomdataValue)).to.equal(false); + }); + var xBadCustomDataZerocd = { + bidder: BIDDER_CODE, + params: { + 'placementId': '1111111110', + 'publisherId': '9876abcd12-3', + 'siteId': '1234567890', + 'customData': [] + } + }; + it('should not validate customData array having no elements', function () { + expect(spec.isBidRequestValid(xBadCustomDataZerocd)).to.equal(false); + }); + var xBadCustomDataNotargeting = { + bidder: BIDDER_CODE, + params: { + 'placementId': '1234567890', + 'publisherId': '9876abcd12-3', + 'customData': [{'settings': {}, 'xx': {'gender': 'bart', 'age': 'low'}}], + siteId: '1234567890' + } + }; + it('should not validate customData[] having no "targeting"', function () { + expect(spec.isBidRequestValid(xBadCustomDataNotargeting)).to.equal(false); + }); + var xBadCustomDataTgtNotObj = { + bidder: BIDDER_CODE, + params: { + 'placementId': '1234567890', + 'publisherId': '9876abcd12-3', + 'customData': [{'settings': {}, 'targeting': 'this should be an object'}], + siteId: '1234567890' + } + }; + it('should not validate customData[0].targeting not being an object', function () { + expect(spec.isBidRequestValid(xBadCustomDataTgtNotObj)).to.equal(false); + }); + var xBadCustomParams = { + bidder: BIDDER_CODE, + params: { + 'placementId': '1234567890', + 'publisherId': '9876abcd12-3', + 'siteId': '1234567890', + 'customParams': 'this key is no longer valid' + } + }; + it('should not validate customParams - this is a renamed key', function () { + expect(spec.isBidRequestValid(xBadCustomParams)).to.equal(false); + }); + }); + describe('buildRequests', function () { + it('sends bid request to NEWSPASSURI via POST', function () { + const request = spec.buildRequests(validBidRequests, validBidderRequest); + expect(request.url).to.equal(NEWSPASSURI); + expect(request.method).to.equal('POST'); + }); + it('sends data as a string', function () { + const request = spec.buildRequests(validBidRequests, validBidderRequest); + expect(request.data).to.be.a('string'); + }); + it('sends all bid parameters', function () { + const request = spec.buildRequests(validBidRequests, validBidderRequest); + expect(request).to.have.all.keys(['bidderRequest', 'data', 'method', 'url']); + }); + it('adds all parameters inside the ext object only', function () { + const request = spec.buildRequests(validBidRequests, validBidderRequest); + expect(request.data).to.be.a('string'); + var data = JSON.parse(request.data); + expect(data.imp[0].ext.newspassid.customData).to.be.an('array'); + expect(request).not.to.have.key('lotameData'); + expect(request).not.to.have.key('customData'); + }); + it('adds all parameters inside the ext object only - lightning', function () { + let localBidReq = JSON.parse(JSON.stringify(validBidRequests)); + const request = spec.buildRequests(localBidReq, validBidderRequest); + expect(request.data).to.be.a('string'); + var data = JSON.parse(request.data); + expect(data.imp[0].ext.newspassid.customData).to.be.an('array'); + expect(request).not.to.have.key('lotameData'); + expect(request).not.to.have.key('customData'); + }); + it('has correct bidder', function () { + const request = spec.buildRequests(validBidRequests, validBidderRequest); + expect(request.bidderRequest.bids[0].bidder).to.equal(BIDDER_CODE); + }); + it('handles mediaTypes element correctly', function () { + const request = spec.buildRequests(validBidRequestsWithBannerMediaType, validBidderRequest); + expect(request).to.have.all.keys(['bidderRequest', 'data', 'method', 'url']); + }); + it('handles no newspassid or custom data', function () { + const request = spec.buildRequests(validBidRequestsMinimal, validBidderRequest); + expect(request).to.have.all.keys(['bidderRequest', 'data', 'method', 'url']); + }); + it('should not crash when there is no sizes element at all', function () { + const request = spec.buildRequests(validBidRequestsNoSizes, validBidderRequest); + expect(request).to.have.all.keys(['bidderRequest', 'data', 'method', 'url']); + }); + it('should be able to handle non-single requests', function () { + config.setConfig({'newspassid': {'singleRequest': false}}); + const request = spec.buildRequests(validBidRequestsNoSizes, validBidderRequest); + expect(request).to.be.a('array'); + expect(request[0]).to.have.all.keys(['bidderRequest', 'data', 'method', 'url']); + config.setConfig({'newspassid': {'singleRequest': true}}); + }); + it('should not have imp[N].ext.newspassid.userId', function () { + let bidderRequest = validBidderRequest; + let bidRequests = validBidRequests; + bidRequests[0]['userId'] = { + 'digitrustid': {data: {id: 'DTID', keyv: 4, privacy: {optout: false}, producer: 'ABC', version: 2}}, + 'id5id': { uid: '1111', ext: { linkType: 2, abTestingControlGroup: false } }, + 'idl_env': '3333', + 'parrableid': 'eidVersion.encryptionKeyReference.encryptedValue', + 'pubcid': '5555', + 'tdid': '6666', + 'sharedid': {'id': '01EAJWWNEPN3CYMM5N8M5VXY22', 'third': '01EAJWWNEPN3CYMM5N8M5VXY22'} + }; + bidRequests[0]['userIdAsEids'] = validBidRequestsWithUserIdData[0]['userIdAsEids']; + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + let firstBid = payload.imp[0].ext.newspassid; + expect(firstBid).to.not.have.property('userId'); + delete validBidRequests[0].userId; // tidy up now, else it will screw with other tests + }); + it('should pick up the value of pubcid when built using the pubCommonId module (not userId)', function () { + let bidRequests = validBidRequests; + bidRequests[0]['userId'] = { + 'digitrustid': {data: {id: 'DTID', keyv: 4, privacy: {optout: false}, producer: 'ABC', version: 2}}, + 'id5id': { uid: '1111', ext: { linkType: 2, abTestingControlGroup: false } }, + 'idl_env': '3333', + 'parrableid': 'eidVersion.encryptionKeyReference.encryptedValue', + 'tdid': '6666', + 'sharedid': {'id': '01EAJWWNEPN3CYMM5N8M5VXY22', 'third': '01EAJWWNEPN3CYMM5N8M5VXY22'} + }; + bidRequests[0]['userIdAsEids'] = validBidRequestsWithUserIdData[0]['userIdAsEids']; + const request = spec.buildRequests(bidRequests, validBidderRequest); + const payload = JSON.parse(request.data); + expect(payload.ext.newspassid.pubcid).to.equal(bidRequests[0]['crumbs']['pubcid']); + delete validBidRequests[0].userId; // tidy up now, else it will screw with other tests + }); + it('should add a user.ext.eids object to contain user ID data in the new location (Nov 2019) Updated Aug 2020', function() { + const request = spec.buildRequests(validBidRequestsWithUserIdData, validBidderRequest); + const payload = JSON.parse(request.data); + expect(payload.user).to.exist; + expect(payload.user.ext).to.exist; + expect(payload.user.ext.eids).to.exist; + expect(payload.user.ext.eids[0]['source']).to.equal('pubcid.org'); + expect(payload.user.ext.eids[0]['uids'][0]['id']).to.equal('12345678'); + expect(payload.user.ext.eids[1]['source']).to.equal('adserver.org'); + expect(payload.user.ext.eids[1]['uids'][0]['id']).to.equal('1111tdid'); + expect(payload.user.ext.eids[2]['source']).to.equal('id5-sync.com'); + expect(payload.user.ext.eids[2]['uids'][0]['id']).to.equal('ID5-someId'); + expect(payload.user.ext.eids[3]['source']).to.equal('criteoId'); + expect(payload.user.ext.eids[3]['uids'][0]['id']).to.equal('1111criteoId'); + expect(payload.user.ext.eids[4]['source']).to.equal('idl_env'); + expect(payload.user.ext.eids[4]['uids'][0]['id']).to.equal('liverampId'); + expect(payload.user.ext.eids[5]['source']).to.equal('lipb'); + expect(payload.user.ext.eids[5]['uids'][0]['id']['lipbid']).to.equal('lipbidId123'); + expect(payload.user.ext.eids[6]['source']).to.equal('parrableId'); + expect(payload.user.ext.eids[6]['uids'][0]['id']['eid']).to.equal('01.5678.parrableid'); + }); + it('replaces the auction url for a config override', function () { + spec.propertyBag.config = null; + let fakeOrigin = 'http://sometestendpoint'; + config.setConfig({'newspassid': {'endpointOverride': {'origin': fakeOrigin}}}); + const request = spec.buildRequests(validBidRequests, validBidderRequest); + expect(request.url).to.equal(fakeOrigin + '/openrtb2/auction'); + expect(request.method).to.equal('POST'); + const data = JSON.parse(request.data); + expect(data.ext.newspassid.origin).to.equal(fakeOrigin); + config.setConfig({'newspassid': {'kvpPrefix': null, 'endpointOverride': null}}); + }); + it('replaces the FULL auction url for a config override', function () { + spec.propertyBag.config = null; + let fakeurl = 'http://sometestendpoint/myfullurl'; + config.setConfig({'newspassid': {'endpointOverride': {'auctionUrl': fakeurl}}}); + const request = spec.buildRequests(validBidRequests, validBidderRequest); + expect(request.url).to.equal(fakeurl); + expect(request.method).to.equal('POST'); + const data = JSON.parse(request.data); + expect(data.ext.newspassid.origin).to.equal(fakeurl); + config.setConfig({'newspassid': {'kvpPrefix': null, 'endpointOverride': null}}); + }); + it('should ignore kvpPrefix', function () { + spec.propertyBag.config = null; + config.setConfig({'newspassid': {'kvpPrefix': 'np'}}); + const request = spec.buildRequests(validBidRequests, validBidderRequest); + const result = spec.interpretResponse(validResponse, request); + expect(result[0].adserverTargeting).to.have.own.property('np_appnexus_crid'); + expect(utils.deepAccess(result[0].adserverTargeting, 'np_appnexus_crid')).to.equal('98493581'); + expect(utils.deepAccess(result[0].adserverTargeting, 'np_adId')).to.equal('2899ec066a91ff8-0-np-0'); + expect(utils.deepAccess(result[0].adserverTargeting, 'np_size')).to.equal('300x600'); + expect(utils.deepAccess(result[0].adserverTargeting, 'np_pb_r')).to.equal('0.50'); + expect(utils.deepAccess(result[0].adserverTargeting, 'np_bid')).to.equal('true'); + config.resetConfig(); + }); + it('should create a meta object on each bid returned', function () { + spec.propertyBag.config = null; + const request = spec.buildRequests(validBidRequests, validBidderRequest); + const result = spec.interpretResponse(validResponse, request); + expect(result[0]).to.have.own.property('meta'); + expect(result[0].meta.advertiserDomains[0]).to.equal('http://prebid.org'); + config.resetConfig(); + }); + it('should use nptestmode GET value if set', function() { + var specMock = utils.deepClone(spec); + specMock.getGetParametersAsObject = function() { + return {'nptestmode': 'mytestvalue_123'}; + }; + const request = specMock.buildRequests(validBidRequests, validBidderRequest); + const data = JSON.parse(request.data); + expect(data.imp[0].ext.newspassid.customData).to.be.an('array'); + expect(data.imp[0].ext.newspassid.customData[0].targeting.nptestmode).to.equal('mytestvalue_123'); + }); + it('should pass through GET params if present: npf, nppf, nprp, npip', function() { + var specMock = utils.deepClone(spec); + specMock.getGetParametersAsObject = function() { + return {npf: '1', nppf: '0', nprp: '2', npip: '123'}; + }; + const request = specMock.buildRequests(validBidRequests, validBidderRequest); + const data = JSON.parse(request.data); + expect(data.ext.newspassid.npf).to.equal(1); + expect(data.ext.newspassid.nppf).to.equal(0); + expect(data.ext.newspassid.nprp).to.equal(2); + expect(data.ext.newspassid.npip).to.equal(123); + }); + it('should pass through GET params if present: npf, nppf, nprp, npip with alternative values', function() { + var specMock = utils.deepClone(spec); + specMock.getGetParametersAsObject = function() { + return {npf: 'false', nppf: 'true', nprp: 'xyz', npip: 'hello'}; + }; + const request = specMock.buildRequests(validBidRequests, validBidderRequest); + const data = JSON.parse(request.data); + expect(data.ext.newspassid.npf).to.equal(0); + expect(data.ext.newspassid.nppf).to.equal(1); + expect(data.ext.newspassid).to.not.haveOwnProperty('nprp'); + expect(data.ext.newspassid).to.not.haveOwnProperty('npip'); + }); + it('should use nptestmode GET value if set, even if there is no customdata in config', function() { + var specMock = utils.deepClone(spec); + specMock.getGetParametersAsObject = function() { + return {'nptestmode': 'mytestvalue_123'}; + }; + const request = specMock.buildRequests(validBidRequestsMinimal, validBidderRequest); + const data = JSON.parse(request.data); + expect(data.imp[0].ext.newspassid.customData).to.be.an('array'); + expect(data.imp[0].ext.newspassid.customData[0].targeting.nptestmode).to.equal('mytestvalue_123'); + }); + it('should use GET values auction=[encoded URL] & cookiesync=[encoded url] if set', function() { + spec.propertyBag.config = null; + var specMock = utils.deepClone(spec); + specMock.getGetParametersAsObject = function() { + return {}; + }; + let request = specMock.buildRequests(validBidRequestsMinimal, validBidderRequest); + let url = request.url; + expect(url).to.equal('https://bidder.newspassid.com/openrtb2/auction'); + let cookieUrl = specMock.getCookieSyncUrl(); + expect(cookieUrl).to.equal('https://bidder.newspassid.com/static/load-cookie.html'); + specMock = utils.deepClone(spec); + specMock.getGetParametersAsObject = function() { + return {'auction': 'https://www.someurl.com/auction', 'cookiesync': 'https://www.someurl.com/sync'}; + }; + request = specMock.buildRequests(validBidRequestsMinimal, validBidderRequest); + url = request.url; + expect(url).to.equal('https://www.someurl.com/auction'); + cookieUrl = specMock.getCookieSyncUrl(); + expect(cookieUrl).to.equal('https://www.someurl.com/sync'); + }); + it('should use a valid npstoredrequest GET value if set to override the placementId values, and set np_rw if we find it', function() { + var specMock = utils.deepClone(spec); + specMock.getGetParametersAsObject = function() { + return {'npstoredrequest': '1122334455'}; // 10 digits are valid + }; + const request = specMock.buildRequests(validBidRequestsMinimal, validBidderRequest); + const data = JSON.parse(request.data); + expect(data.ext.newspassid.np_rw).to.equal(1); + expect(data.imp[0].ext.prebid.storedrequest.id).to.equal('1122334455'); + }); + it('should NOT use an invalid npstoredrequest GET value if set to override the placementId values, and set np_rw to 0', function() { + var specMock = utils.deepClone(spec); + specMock.getGetParametersAsObject = function() { + return {'npstoredrequest': 'BADVAL'}; // 10 digits are valid + }; + const request = specMock.buildRequests(validBidRequestsMinimal, validBidderRequest); + const data = JSON.parse(request.data); + expect(data.ext.newspassid.np_rw).to.equal(0); + expect(data.imp[0].ext.prebid.storedrequest.id).to.equal('1310000099'); + }); + it('should pick up the config value of coppa & set it in the request', function () { + config.setConfig({'coppa': true}); + const request = spec.buildRequests(validBidRequestsNoSizes, validBidderRequest); + const payload = JSON.parse(request.data); + expect(payload.regs).to.include.keys('coppa'); + expect(payload.regs.coppa).to.equal(1); + config.resetConfig(); + }); + it('should pick up the config value of coppa & only set it in the request if its true', function () { + config.setConfig({'coppa': false}); + const request = spec.buildRequests(validBidRequestsNoSizes, validBidderRequest); + const payload = JSON.parse(request.data); + expect(utils.deepAccess(payload, 'regs.coppa')).to.be.undefined; + config.resetConfig(); + }); + it('should should contain a unique page view id in the auction request which persists across calls', function () { + let request = spec.buildRequests(validBidRequests, validBidderRequest); + let payload = JSON.parse(request.data); + expect(utils.deepAccess(payload, 'ext.newspassid.pv')).to.be.a('string'); + request = spec.buildRequests(validBidRequestsIsThisCamelCaseEnough, validBidderRequest); + let payload2 = JSON.parse(request.data); + expect(utils.deepAccess(payload2, 'ext.newspassid.pv')).to.be.a('string'); + expect(utils.deepAccess(payload2, 'ext.newspassid.pv')).to.equal(utils.deepAccess(payload, 'ext.newspassid.pv')); + }); + it('should indicate that the whitelist was used when it contains valid data', function () { + config.setConfig({'newspassid': {'np_whitelist_adserver_keys': ['np_appnexus_pb', 'np_appnexus_imp_id']}}); + const request = spec.buildRequests(validBidRequests, validBidderRequest); + const payload = JSON.parse(request.data); + expect(payload.ext.newspassid.np_kvp_rw).to.equal(1); + config.resetConfig(); + }); + it('should indicate that the whitelist was not used when it contains no data', function () { + config.setConfig({'newspassid': {'np_whitelist_adserver_keys': []}}); + const request = spec.buildRequests(validBidRequests, validBidderRequest); + const payload = JSON.parse(request.data); + expect(payload.ext.newspassid.np_kvp_rw).to.equal(0); + config.resetConfig(); + }); + it('should indicate that the whitelist was not used when it is not set in the config', function () { + const request = spec.buildRequests(validBidRequests, validBidderRequest); + const payload = JSON.parse(request.data); + expect(payload.ext.newspassid.np_kvp_rw).to.equal(0); + }); + it('should handle ortb2 site data', function () { + let bidderRequest = JSON.parse(JSON.stringify(validBidderRequest)); + bidderRequest.ortb2 = { + 'site': { + 'name': 'example_ortb2_name', + 'domain': 'page.example.com', + 'cat': ['IAB2'], + 'sectioncat': ['IAB2-2'], + 'pagecat': ['IAB2-2'], + 'page': 'https://page.example.com/here.html', + 'ref': 'https://ref.example.com', + 'keywords': 'power tools, drills', + 'search': 'drill' + } + }; + const request = spec.buildRequests(validBidRequests, bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.imp[0].ext.newspassid.customData[0].targeting.name).to.equal('example_ortb2_name'); + expect(payload.user.ext).to.not.have.property('gender'); + }); + it('should add ortb2 site data when there is no customData already created', function () { + let bidderRequest = JSON.parse(JSON.stringify(validBidderRequest)); + bidderRequest.ortb2 = { + 'site': { + 'name': 'example_ortb2_name', + 'domain': 'page.example.com', + 'cat': ['IAB2'], + 'sectioncat': ['IAB2-2'], + 'pagecat': ['IAB2-2'], + 'page': 'https://page.example.com/here.html', + 'ref': 'https://ref.example.com', + 'keywords': 'power tools, drills', + 'search': 'drill' + } + }; + const request = spec.buildRequests(validBidRequestsNoCustomData, bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.imp[0].ext.newspassid.customData[0].targeting.name).to.equal('example_ortb2_name'); + expect(payload.imp[0].ext.newspassid.customData[0].targeting).to.not.have.property('gender') + }); + it('should add ortb2 user data to the user object', function () { + let bidderRequest = JSON.parse(JSON.stringify(validBidderRequest)); + bidderRequest.ortb2 = { + 'user': { + 'gender': 'I identify as a box of rocks' + } + }; + const request = spec.buildRequests(validBidRequests, bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.user.gender).to.equal('I identify as a box of rocks'); + }); + it('handles schain object in each bidrequest (will be the same in each br)', function () { + let br = JSON.parse(JSON.stringify(validBidRequests)); + let schainConfigObject = { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'bidderA.com', + 'sid': '00001', + 'hp': 1 + } + ] + }; + br[0]['schain'] = schainConfigObject; + const request = spec.buildRequests(br, validBidderRequest); + const data = JSON.parse(request.data); + expect(data.source.ext).to.haveOwnProperty('schain'); + expect(data.source.ext.schain).to.deep.equal(schainConfigObject); // .deep.equal() : Target object deeply (but not strictly) equals `{a: 1}` + }); + }); + describe('interpretResponse', function () { + it('should build bid array', function () { + const request = spec.buildRequests(validBidRequests, validBidderRequest); + const result = spec.interpretResponse(validResponse, request); + expect(result.length).to.equal(1); + }); + it('should have all relevant fields', function () { + const request = spec.buildRequests(validBidRequests, validBidderRequest); + const result = spec.interpretResponse(validResponse, request); + const bid = result[0]; + expect(bid.cpm).to.equal(validResponse.body.seatbid[0].bid[0].cpm); + expect(bid.width).to.equal(validResponse.body.seatbid[0].bid[0].width); + expect(bid.height).to.equal(validResponse.body.seatbid[0].bid[0].height); + }); + it('should build bid array with usp/CCPA', function () { + let validBR = JSON.parse(JSON.stringify(validBidderRequest)); + validBR.uspConsent = '1YNY'; + const request = spec.buildRequests(validBidRequests, validBR); + const payload = JSON.parse(request.data); + expect(payload.user.ext.uspConsent).not.to.exist; + expect(payload.regs.ext.us_privacy).to.equal('1YNY'); + }); + it('should fail ok if no seatbid in server response', function () { + const result = spec.interpretResponse({}, {}); + expect(result).to.be.an('array'); + expect(result).to.be.empty; + }); + it('should fail ok if seatbid is not an array', function () { + const result = spec.interpretResponse({'body': {'seatbid': 'nothing_here'}}, {}); + expect(result).to.be.an('array'); + expect(result).to.be.empty; + }); + it('should correctly parse response where there are more bidders than ad slots', function () { + const request = spec.buildRequests(validBidRequests, validBidderRequest); + const result = spec.interpretResponse(validBidResponse1adWith2Bidders, request); + expect(result.length).to.equal(2); + }); + it('should have a ttl of 600', function () { + const request = spec.buildRequests(validBidRequests, validBidderRequest); + const result = spec.interpretResponse(validResponse, request); + expect(result[0].ttl).to.equal(300); + }); + it('should handle a valid whitelist, removing items not on the list & leaving others', function () { + config.setConfig({'newspassid': {'np_whitelist_adserver_keys': ['np_appnexus_crid', 'np_appnexus_adId']}}); + const request = spec.buildRequests(validBidRequests, validBidderRequest); + const result = spec.interpretResponse(validResponse, request); + expect(utils.deepAccess(result[0].adserverTargeting, 'np_appnexus_adv')).to.be.undefined; + expect(utils.deepAccess(result[0].adserverTargeting, 'np_appnexus_adId')).to.equal('2899ec066a91ff8-0-np-0'); + config.resetConfig(); + }); + it('should ignore a whitelist if enhancedAdserverTargeting is false', function () { + config.setConfig({'newspassid': {'np_whitelist_adserver_keys': ['np_appnexus_crid', 'np_appnexus_imp_id'], 'enhancedAdserverTargeting': false}}); + const request = spec.buildRequests(validBidRequests, validBidderRequest); + const result = spec.interpretResponse(validResponse, request); + expect(utils.deepAccess(result[0].adserverTargeting, 'np_appnexus_adv')).to.be.undefined; + expect(utils.deepAccess(result[0].adserverTargeting, 'np_appnexus_imp_id')).to.be.undefined; + config.resetConfig(); + }); + it('should correctly handle enhancedAdserverTargeting being false', function () { + config.setConfig({'newspassid': {'enhancedAdserverTargeting': false}}); + const request = spec.buildRequests(validBidRequests, validBidderRequest); + const result = spec.interpretResponse(validResponse, request); + expect(utils.deepAccess(result[0].adserverTargeting, 'np_appnexus_adv')).to.be.undefined; + expect(utils.deepAccess(result[0].adserverTargeting, 'np_appnexus_imp_id')).to.be.undefined; + config.resetConfig(); + }); + it('should add unique adId values to each bid', function() { + const request = spec.buildRequests(validBidRequests, validBidderRequest); + let validres = JSON.parse(JSON.stringify(validResponse2BidsSameAdunit)); + const result = spec.interpretResponse(validres, request); + expect(result.length).to.equal(1); + expect(result[0]['price']).to.equal(0.9); + expect(result[0]['adserverTargeting']['np_npappnexus_adId']).to.equal('2899ec066a91ff8-0-np-1'); + }); + it('should correctly process an auction with 2 adunits & multiple bidders one of which bids for both adslots', function() { + let validres = JSON.parse(JSON.stringify(multiResponse1)); + let request = spec.buildRequests(multiRequest1, multiBidderRequest1.bidderRequest); + let result = spec.interpretResponse(validres, request); + expect(result.length).to.equal(4); // one of the 5 bids will have been removed + expect(result[1]['price']).to.equal(0.521); + expect(result[1]['impid']).to.equal('3025f169863b7f8'); + expect(result[1]['id']).to.equal('18552976939844999'); + expect(result[1]['adserverTargeting']['np_npappnexus_adId']).to.equal('3025f169863b7f8-0-np-2'); + validres = JSON.parse(JSON.stringify(multiResponse1)); + validres.body.seatbid[0].bid[1].price = 1.1; + validres.body.seatbid[0].bid[1].cpm = 1.1; + request = spec.buildRequests(multiRequest1, multiBidderRequest1.bidderRequest); + result = spec.interpretResponse(validres, request); + expect(result[1]['price']).to.equal(1.1); + expect(result[1]['impid']).to.equal('3025f169863b7f8'); + expect(result[1]['id']).to.equal('18552976939844681'); + expect(result[1]['adserverTargeting']['np_npappnexus_adId']).to.equal('3025f169863b7f8-0-np-1'); + }); + }); + describe('userSyncs', function () { + it('should fail gracefully if no server response', function () { + const result = spec.getUserSyncs('bad', false, emptyObject); + expect(result).to.be.empty; + }); + it('should fail gracefully if server response is empty', function () { + const result = spec.getUserSyncs('bad', [], emptyObject); + expect(result).to.be.empty; + }); + it('should append the various values if they exist', function() { + spec.buildRequests(validBidRequests, validBidderRequest); + const result = spec.getUserSyncs({iframeEnabled: true}, 'good server response', emptyObject); + expect(result).to.be.an('array'); + expect(result[0].url).to.include('publisherId=9876abcd12-3'); + expect(result[0].url).to.include('siteId=1234567890'); + }); + it('should append ccpa (usp data)', function() { + spec.buildRequests(validBidRequests, validBidderRequest); + const result = spec.getUserSyncs({iframeEnabled: true}, 'good server response', emptyObject, '1YYN'); + expect(result).to.be.an('array'); + expect(result[0].url).to.include('usp_consent=1YYN'); + }); + it('should use "" if no usp is sent to cookieSync', function() { + spec.buildRequests(validBidRequests, validBidderRequest); + const result = spec.getUserSyncs({iframeEnabled: true}, 'good server response', emptyObject); + expect(result).to.be.an('array'); + expect(result[0].url).to.include('usp_consent=&'); + }); + }); + describe('default size', function () { + it('should should return default sizes if no obj is sent', function () { + let obj = ''; + const result = defaultSize(obj); + expect(result.defaultHeight).to.equal(250); + expect(result.defaultWidth).to.equal(300); + }); + }); + describe('getGranularityKeyName', function() { + it('should return a string granularity as-is', function() { + const result = getGranularityKeyName('', 'this is it', ''); + expect(result).to.equal('this is it'); + }); + it('should return "custom" for a mediaTypeGranularity object', function() { + const result = getGranularityKeyName('', {}, ''); + expect(result).to.equal('custom'); + }); + it('should return "custom" for a mediaTypeGranularity object', function() { + const result = getGranularityKeyName('', false, 'string buckets'); + expect(result).to.equal('string buckets'); + }); + }); + describe('getGranularityObject', function() { + it('should return an object as-is', function() { + const result = getGranularityObject('', {'name': 'mark'}, '', ''); + expect(result.name).to.equal('mark'); + }); + it('should return an object as-is', function() { + const result = getGranularityObject('', false, 'custom', {'name': 'rupert'}); + expect(result.name).to.equal('rupert'); + }); + }); + describe('blockTheRequest', function() { + it('should return true if np_request is false', function() { + config.setConfig({'newspassid': {'np_request': false}}); + let result = spec.blockTheRequest(); + expect(result).to.be.true; + config.resetConfig(); + }); + it('should return false if np_request is true', function() { + config.setConfig({'newspassid': {'np_request': true}}); + let result = spec.blockTheRequest(); + expect(result).to.be.false; + config.resetConfig(); + }); + }); + describe('getPageId', function() { + it('should return the same Page ID for multiple calls', function () { + let result = spec.getPageId(); + expect(result).to.be.a('string'); + let result2 = spec.getPageId(); + expect(result2).to.equal(result); + }); + }); + describe('getBidRequestForBidId', function() { + it('should locate a bid inside a bid array', function () { + let result = spec.getBidRequestForBidId('2899ec066a91ff8', validBidRequestsMulti); + expect(result.testId).to.equal(1); + result = spec.getBidRequestForBidId('2899ec066a91ff0', validBidRequestsMulti); + expect(result.testId).to.equal(2); + }); + }); + describe('removeSingleBidderMultipleBids', function() { + it('should remove the multi bid by npappnexus for adslot 2d30e86db743a8', function() { + let validres = JSON.parse(JSON.stringify(multiResponse1)); + expect(validres.body.seatbid[0].bid.length).to.equal(3); + expect(validres.body.seatbid[0].seat).to.equal('npappnexus'); + let response = spec.removeSingleBidderMultipleBids(validres.body.seatbid); + expect(response.length).to.equal(2); + expect(response[0].bid.length).to.equal(2); + expect(response[0].seat).to.equal('npappnexus'); + expect(response[1].bid.length).to.equal(2); + }); + }); +}); diff --git a/test/spec/modules/openxOrtbBidAdapter_spec.js b/test/spec/modules/openxOrtbBidAdapter_spec.js new file mode 100644 index 00000000000..b3f63d9c23a --- /dev/null +++ b/test/spec/modules/openxOrtbBidAdapter_spec.js @@ -0,0 +1,1303 @@ +import {expect} from 'chai'; +import {spec, REQUEST_URL, SYNC_URL, DEFAULT_PH} from 'modules/openxOrtbBidAdapter.js'; +import {newBidder} from 'src/adapters/bidderFactory.js'; +import {BANNER, VIDEO} from 'src/mediaTypes.js'; +import {config} from 'src/config.js'; +import * as utils from 'src/utils.js'; + +const DEFAULT_SYNC = SYNC_URL + '?ph=' + DEFAULT_PH; + +describe('OpenxRtbAdapter', function () { + const adapter = newBidder(spec); + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid()', function () { + describe('when request is for a banner ad', function () { + let bannerBid; + beforeEach(function () { + bannerBid = { + bidder: 'openx', + params: {}, + adUnitCode: 'adunit-code', + mediaTypes: {banner: {}}, + sizes: [[300, 250], [300, 600]], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475' + }; + }); + + it('should return false when there is no delivery domain', function () { + bannerBid.params = {'unit': '12345678'}; + expect(spec.isBidRequestValid(bannerBid)).to.equal(false); + }); + + describe('when there is a delivery domain', function () { + beforeEach(function () { + bannerBid.params = {delDomain: 'test-delivery-domain'} + }); + + it('should return false when there is no ad unit id and size', function () { + expect(spec.isBidRequestValid(bannerBid)).to.equal(false); + }); + + it('should return true if there is an adunit id ', function () { + bannerBid.params.unit = '12345678'; + expect(spec.isBidRequestValid(bannerBid)).to.equal(true); + }); + + it('should return true if there is no adunit id and sizes are defined', function () { + bannerBid.mediaTypes.banner.sizes = [720, 90]; + expect(spec.isBidRequestValid(bannerBid)).to.equal(true); + }); + + it('should return false if no sizes are defined ', function () { + expect(spec.isBidRequestValid(bannerBid)).to.equal(false); + }); + + it('should return false if sizes empty ', function () { + bannerBid.mediaTypes.banner.sizes = []; + expect(spec.isBidRequestValid(bannerBid)).to.equal(false); + }); + }); + }); + + describe('when request is for a multiformat ad', function () { + describe('and request config uses mediaTypes video and banner', () => { + const multiformatBid = { + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250]] + }, + video: { + playerSize: [300, 250] + } + }, + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + transactionId: '4008d88a-8137-410b-aa35-fbfdabcb478e' + }; + it('should return true multisize when required params found', function () { + expect(spec.isBidRequestValid(multiformatBid)).to.equal(true); + }); + }); + }); + + describe('when request is for a video ad', function () { + describe('and request config uses mediaTypes', () => { + const videoBidWithMediaTypes = { + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + video: { + playerSize: [640, 480] + } + }, + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + transactionId: '4008d88a-8137-410b-aa35-fbfdabcb478e' + }; + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(videoBidWithMediaTypes)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let videoBidWithMediaTypes = Object.assign({}, videoBidWithMediaTypes); + videoBidWithMediaTypes.params = {}; + expect(spec.isBidRequestValid(videoBidWithMediaTypes)).to.equal(false); + }); + }); + describe('and request config uses both delDomain and platform', () => { + const videoBidWithDelDomainAndPlatform = { + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain', + platform: '1cabba9e-cafe-3665-beef-f00f00f00f00' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + video: { + playerSize: [640, 480] + } + }, + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + transactionId: '4008d88a-8137-410b-aa35-fbfdabcb478e' + }; + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(videoBidWithDelDomainAndPlatform)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let videoBidWithMediaTypes = Object.assign({}, videoBidWithDelDomainAndPlatform); + videoBidWithMediaTypes.params = {}; + expect(spec.isBidRequestValid(videoBidWithMediaTypes)).to.equal(false); + }); + }); + describe('and request config uses mediaType', () => { + const videoBidWithMediaType = { + 'bidder': 'openx', + 'params': { + 'unit': '12345678', + 'delDomain': 'test-del-domain' + }, + 'adUnitCode': 'adunit-code', + 'mediaType': 'video', + 'sizes': [640, 480], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'transactionId': '4008d88a-8137-410b-aa35-fbfdabcb478e' + }; + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(videoBidWithMediaType)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let videoBidWithMediaType = Object.assign({}, videoBidWithMediaType); + delete videoBidWithMediaType.params; + videoBidWithMediaType.params = {}; + expect(spec.isBidRequestValid(videoBidWithMediaType)).to.equal(false); + }); + }); + }); + }); + + describe('buildRequests()', function () { + let bidRequestsWithMediaTypes; + let bidRequestsWithPlatform; + let mockBidderRequest; + + beforeEach(function () { + mockBidderRequest = {refererInfo: {}}; + + bidRequestsWithMediaTypes = [{ + bidder: 'openx', + params: { + unit: '11', + delDomain: 'test-del-domain', + platform: '1cabba9e-cafe-3665-beef-f00f00f00f00', + }, + adUnitCode: '/adunit-code/test-path', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bidId: 'test-bid-id-1', + bidderRequestId: 'test-bid-request-1', + auctionId: 'test-auction-1', + transactionId: 'test-transactionId-1' + }, { + bidder: 'openx', + params: { + unit: '22', + delDomain: 'test-del-domain', + platform: '1cabba9e-cafe-3665-beef-f00f00f00f00', + }, + adUnitCode: 'adunit-code', + mediaTypes: { + video: { + playerSize: [640, 480] + } + }, + bidId: 'test-bid-id-2', + bidderRequestId: 'test-bid-request-2', + auctionId: 'test-auction-2', + transactionId: 'test-transactionId-2' + }]; + }); + + context('common requests checks', function() { + it('should send bid request to openx url via POST', function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + expect(request[0].url).to.equal(REQUEST_URL); + expect(request[0].method).to.equal('POST'); + }); + + it('should send delivery domain, if available', function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + expect(request[0].data.ext.delDomain).to.equal(bidRequestsWithMediaTypes[0].params.delDomain); + expect(request[0].data.ext.platformId).to.be.undefined; + }); + + it('should send platform id, if available', function () { + bidRequestsWithMediaTypes[0].params.platform = '1cabba9e-cafe-3665-beef-f00f00f00f00'; + bidRequestsWithMediaTypes[1].params.platform = '1cabba9e-cafe-3665-beef-f00f00f00f00'; + + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + expect(request[0].data.ext.platform).to.equal(bidRequestsWithMediaTypes[0].params.platform); + expect(request[1].data.ext.platform).to.equal(bidRequestsWithMediaTypes[0].params.platform); + }); + + it('should send openx adunit codes', function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + expect(request[0].data.imp[0].tagid).to.equal(bidRequestsWithMediaTypes[0].params.unit); + expect(request[1].data.imp[0].tagid).to.equal(bidRequestsWithMediaTypes[1].params.unit); + }); + + it('should send out custom params on bids that have customParams specified', function () { + const bidRequest = Object.assign({}, + bidRequestsWithMediaTypes[0], + { + params: { + unit: '12345678', + delDomain: 'test-del-domain', + customParams: {'Test1': 'testval1+', 'test2': ['testval2/', 'testval3']} + } + } + ); + + mockBidderRequest.bids = [bidRequest]; + const request = spec.buildRequests([bidRequest], mockBidderRequest); + expect(request[0].data.imp[0].ext.customParams).to.equal(bidRequest.params.customParams); + }) + + describe('floors', function () { + it('should send out custom floors on bids that have customFloors, no currency as account currency is used', function () { + const bidRequest = Object.assign({}, + bidRequestsWithMediaTypes[0], + { + params: { + unit: '12345678', + delDomain: 'test-del-domain', + customFloor: 1.500 + } + } + ); + + const request = spec.buildRequests([bidRequest], mockBidderRequest); + expect(request[0].data.imp[0].bidfloor).to.equal(bidRequest.params.customFloor); + expect(request[0].data.imp[0].bidfloorcur).to.equal(undefined); + }); + + context('with floors module', function () { + let adServerCurrencyStub; + + beforeEach(function () { + adServerCurrencyStub = sinon + .stub(config, 'getConfig') + .withArgs('currency.adServerCurrency') + }); + + afterEach(function () { + config.getConfig.restore(); + }); + + it('should send out floors on bids in USD', function () { + const bidRequest = Object.assign({}, + bidRequestsWithMediaTypes[0], + { + getFloor: () => { + return { + currency: 'USD', + floor: 9.99 + } + } + } + ); + + const request = spec.buildRequests([bidRequest], mockBidderRequest); + expect(request[0].data.imp[0].bidfloor).to.equal(9.99); + expect(request[0].data.imp[0].bidfloorcur).to.equal('USD'); + }); + + it('should send not send floors', function () { + adServerCurrencyStub.returns('EUR'); + const bidRequest = Object.assign({}, + bidRequestsWithMediaTypes[0], + { + getFloor: () => { + return { + currency: 'BTC', + floor: 9.99 + } + } + } + ); + + const request = spec.buildRequests([bidRequest], mockBidderRequest); + expect(request[0].data.imp[0].bidfloor).to.equal(undefined) + expect(request[0].data.imp[0].bidfloorcur).to.equal(undefined) + }); + }) + }) + + describe('FPD', function() { + let bidRequests; + const mockBidderRequest = {refererInfo: {}}; + + beforeEach(function () { + bidRequests = [{ + bidder: 'openx', + params: { + unit: '12345678-banner', + delDomain: 'test-del-domain' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id', + transactionId: 'test-transaction-id-1' + }, { + bidder: 'openx', + mediaTypes: { + video: { + playerSize: [640, 480] + } + }, + params: { + unit: '12345678-video', + delDomain: 'test-del-domain' + }, + 'adUnitCode': 'adunit-code', + + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id', + transactionId: 'test-transaction-id-2' + }]; + }); + + it('ortb2.site should be merged in the request', function() { + const ortb2 = { + site: { + domain: 'page.example.com', + cat: ['IAB2'], + sectioncat: ['IAB2-2'] + } + }; + const request = spec.buildRequests(bidRequests, {refererInfo: {}, ortb2: ortb2}); + let data = request[0].data; + expect(data.site.domain).to.equal('page.example.com'); + expect(data.site.cat).to.deep.equal(['IAB2']); + expect(data.site.sectioncat).to.deep.equal(['IAB2-2']); + }); + + it('ortb2.user should be merged in the request', function() { + const ortb2 = { + user: { + yob: 1985 + } + }; + const request = spec.buildRequests(bidRequests, {refererInfo: {}, ortb2: ortb2}); + let data = request[0].data; + expect(data.user.yob).to.equal(1985); + }); + + describe('ortb2Imp', function() { + describe('ortb2Imp.ext.data.pbadslot', function() { + beforeEach(function () { + if (bidRequests[0].hasOwnProperty('ortb2Imp')) { + delete bidRequests[0].ortb2Imp; + } + }); + + it('should not send if imp[].ext.data object is invalid', function() { + bidRequests[0].ortb2Imp = { + ext: {} + }; + const request = spec.buildRequests(bidRequests, mockBidderRequest); + let data = request[0].data; + expect(data.imp[0].ext).to.not.have.property('data'); + }); + + it('should not send if imp[].ext.data.pbadslot is undefined', function() { + bidRequests[0].ortb2Imp = { + ext: { + data: { + } + } + }; + const request = spec.buildRequests(bidRequests, mockBidderRequest); + let data = request[0].data; + if (data.imp[0].ext.data) { + expect(data.imp[0].ext.data).to.not.have.property('pbadslot'); + } else { + expect(data.imp[0].ext).to.not.have.property('data'); + } + }); + + it('should send if imp[].ext.data.pbadslot is string', function() { + bidRequests[0].ortb2Imp = { + ext: { + data: { + pbadslot: 'abcd' + } + } + }; + const request = spec.buildRequests(bidRequests, mockBidderRequest); + let data = request[0].data; + expect(data.imp[0].ext.data).to.have.property('pbadslot'); + expect(data.imp[0].ext.data.pbadslot).to.equal('abcd'); + }); + }); + + describe('ortb2Imp.ext.data.adserver', function() { + beforeEach(function () { + if (bidRequests[0].hasOwnProperty('ortb2Imp')) { + delete bidRequests[0].ortb2Imp; + } + }); + + it('should not send if imp[].ext.data object is invalid', function() { + bidRequests[0].ortb2Imp = { + ext: {} + }; + const request = spec.buildRequests(bidRequests, mockBidderRequest); + let data = request[0].data; + expect(data.imp[0].ext).to.not.have.property('data'); + }); + + it('should not send if imp[].ext.data.adserver is undefined', function() { + bidRequests[0].ortb2Imp = { + ext: { + data: { + } + } + }; + const request = spec.buildRequests(bidRequests, mockBidderRequest); + let data = request[0].data; + if (data.imp[0].ext.data) { + expect(data.imp[0].ext.data).to.not.have.property('adserver'); + } else { + expect(data.imp[0].ext).to.not.have.property('data'); + } + }); + + it('should send', function() { + let adSlotValue = 'abc'; + bidRequests[0].ortb2Imp = { + ext: { + data: { + adserver: { + name: 'GAM', + adslot: adSlotValue + } + } + } + }; + const request = spec.buildRequests(bidRequests, mockBidderRequest); + let data = request[0].data; + expect(data.imp[0].ext.data.adserver.name).to.equal('GAM'); + expect(data.imp[0].ext.data.adserver.adslot).to.equal(adSlotValue); + }); + }); + + describe('ortb2Imp.ext.data.other', function() { + beforeEach(function () { + if (bidRequests[0].hasOwnProperty('ortb2Imp')) { + delete bidRequests[0].ortb2Imp; + } + }); + + it('should not send if imp[].ext.data object is invalid', function() { + bidRequests[0].ortb2Imp = { + ext: {} + }; + const request = spec.buildRequests(bidRequests, mockBidderRequest); + let data = request[0].data; + expect(data.imp[0].ext).to.not.have.property('data'); + }); + + it('should not send if imp[].ext.data.other is undefined', function() { + bidRequests[0].ortb2Imp = { + ext: { + data: { + } + } + }; + const request = spec.buildRequests(bidRequests, mockBidderRequest); + let data = request[0].data; + if (data.imp[0].ext.data) { + expect(data.imp[0].ext.data).to.not.have.property('other'); + } else { + expect(data.imp[0].ext).to.not.have.property('data'); + } + }); + + it('ortb2Imp.ext.data.other', function() { + bidRequests[0].ortb2Imp = { + ext: { + data: { + other: 1234 + } + } + }; + const request = spec.buildRequests(bidRequests, mockBidderRequest); + let data = request[0].data; + expect(data.imp[0].ext.data.other).to.equal(1234); + }); + }); + }); + }); + + context('when there is a consent management framework', function () { + let bidRequests; + let mockConfig; + let bidderRequest; + + beforeEach(function () { + bidRequests = [{ + bidder: 'openx', + params: { + unit: '12345678-banner', + delDomain: 'test-del-domain' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id', + transactionId: 'test-transaction-id-1' + }, { + bidder: 'openx', + mediaTypes: { + video: { + playerSize: [640, 480] + } + }, + params: { + unit: '12345678-video', + delDomain: 'test-del-domain' + }, + 'adUnitCode': 'adunit-code', + + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id', + transactionId: 'test-transaction-id-2' + }]; + }); + + afterEach(function () { + config.getConfig.restore(); + }); + + describe('us_privacy', function () { + beforeEach(function () { + bidderRequest = { + uspConsent: '1YYN', + refererInfo: {} + }; + + sinon.stub(config, 'getConfig').callsFake((key) => { + return utils.deepAccess(mockConfig, key); + }); + }); + + it('should send a signal to specify that US Privacy applies to this request', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request[0].data.regs.ext.us_privacy).to.equal('1YYN'); + expect(request[1].data.regs.ext.us_privacy).to.equal('1YYN'); + }); + + it('should not send the regs object, when consent string is undefined', function () { + delete bidderRequest.uspConsent; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request[0].data.regs).to.not.have.property('ext'); + }); + }); + + describe('GDPR', function () { + beforeEach(function () { + bidderRequest = { + gdprConsent: { + consentString: 'test-gdpr-consent-string', + addtlConsent: 'test-addtl-consent-string', + gdprApplies: true + }, + refererInfo: {} + }; + + mockConfig = { + consentManagement: { + cmpApi: 'iab', + timeout: 1111, + allowAuctionWithoutConsent: 'cancel' + } + }; + + sinon.stub(config, 'getConfig').callsFake((key) => { + return utils.deepAccess(mockConfig, key); + }); + }); + + it('should send a signal to specify that GDPR applies to this request', function () { + bidderRequest.bids = bidRequests; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request[0].data.regs.ext.gdpr).to.equal(1); + expect(request[1].data.regs.ext.gdpr).to.equal(1); + }); + + it('should send the consent string', function () { + bidderRequest.bids = bidRequests; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request[0].data.user.ext.consent).to.equal(bidderRequest.gdprConsent.consentString); + expect(request[1].data.user.ext.consent).to.equal(bidderRequest.gdprConsent.consentString); + }); + + it('should send the addtlConsent string', function () { + bidderRequest.bids = bidRequests; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request[0].data.user.ext.ConsentedProvidersSettings.consented_providers).to.equal(bidderRequest.gdprConsent.addtlConsent); + expect(request[1].data.user.ext.ConsentedProvidersSettings.consented_providers).to.equal(bidderRequest.gdprConsent.addtlConsent); + }); + + it('should send a signal to specify that GDPR does not apply to this request', function () { + bidderRequest.gdprConsent.gdprApplies = false; + bidderRequest.bids = bidRequests; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request[0].data.regs.ext.gdpr).to.equal(0); + expect(request[1].data.regs.ext.gdpr).to.equal(0); + }); + + it('when GDPR application is undefined, should not send a signal to specify whether GDPR applies to this request, ' + + 'but can send consent data, ', function () { + delete bidderRequest.gdprConsent.gdprApplies; + bidderRequest.bids = bidRequests; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request[0].data.regs).to.not.have.property('ext'); + expect(request[1].data.regs).to.not.have.property('ext'); + expect(request[0].data.user.ext.consent).to.equal(bidderRequest.gdprConsent.consentString); + expect(request[1].data.user.ext.consent).to.equal(bidderRequest.gdprConsent.consentString); + }); + + it('when consent string is undefined, should not send the consent string, ', function () { + delete bidderRequest.gdprConsent.consentString; + bidderRequest.bids = bidRequests; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request[0].data.imp[0].ext.consent).to.equal(undefined); + expect(request[1].data.imp[0].ext.consent).to.equal(undefined); + }); + }); + }); + + context('coppa', function() { + it('when there are no coppa param settings, should not send a coppa flag', function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + expect(request[0].data.regs.coppa).to.equal(0); + }); + + it('should send a coppa flag there is when there is coppa param settings in the bid requests', function () { + let mockConfig = { + coppa: true + }; + + sinon.stub(config, 'getConfig').callsFake((key) => { + return utils.deepAccess(mockConfig, key); + }); + + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + expect(request[0].data.regs.coppa).to.equal(1); + + config.getConfig.restore(); + }); + }); + + context('do not track (DNT)', function() { + let doNotTrackStub; + + beforeEach(function () { + doNotTrackStub = sinon.stub(utils, 'getDNT'); + }); + afterEach(function() { + doNotTrackStub.restore(); + }); + + it('when there is a do not track, should send a dnt', function () { + doNotTrackStub.returns(1); + + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + expect(request[0].data.device.dnt).to.equal(1); + }); + + it('when there is not do not track, don\'t send dnt', function () { + doNotTrackStub.returns(0); + + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + expect(request[0].data.device.dnt).to.equal(0); + }); + + it('when there is no defined do not track, don\'t send dnt', function () { + doNotTrackStub.returns(null); + + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + expect(request[0].data.device.dnt).to.equal(0); + }); + }); + + context('supply chain (schain)', function () { + let bidRequests; + let schainConfig; + const supplyChainNodePropertyOrder = ['asi', 'sid', 'hp', 'rid', 'name', 'domain']; + + beforeEach(function () { + schainConfig = { + ver: '1.0', + complete: 1, + nodes: [ + { + asi: 'exchange1.com', + sid: '1234', + hp: 1, + rid: 'bid-request-1', + name: 'publisher', + domain: 'publisher.com' + // omitted ext + }, + { + asi: 'exchange2.com', + sid: 'abcd', + hp: 1, + rid: 'bid-request-2', + // name field missing + domain: 'intermediary.com' + }, + { + asi: 'exchange3.com', + sid: '4321', + hp: 1, + // request id + // name field missing + domain: 'intermediary-2.com' + } + ] + }; + + bidRequests = [{ + bidder: 'openx', + params: { + unit: '11', + delDomain: 'test-del-domain' + }, + adUnitCode: '/adunit-code/test-path', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bidId: 'test-bid-id-1', + bidderRequestId: 'test-bid-request-1', + auctionId: 'test-auction-1', + schain: schainConfig + }]; + }); + + it('should send a supply chain object', function () { + const request = spec.buildRequests(bidRequests, mockBidderRequest); + expect(request[0].data.source.ext.schain).to.equal(schainConfig); + }); + + it('should send the supply chain object with the right version', function () { + const request = spec.buildRequests(bidRequests, mockBidderRequest); + expect(request[0].data.source.ext.schain.ver).to.equal(schainConfig.ver); + }); + + it('should send the supply chain object with the right complete value', function () { + const request = spec.buildRequests(bidRequests, mockBidderRequest); + expect(request[0].data.source.ext.schain.complete).to.equal(schainConfig.complete); + }); + }); + + context('when there are userid providers', function () { + const userIdAsEids = [ + { + source: 'adserver.org', + uids: [{ + id: 'some-random-id-value', + atype: 1, + ext: { + rtiPartner: 'TDID' + } + }] + }, + { + source: 'id5-sync.com', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] + }, + { + source: 'sharedid.org', + uids: [{ + id: 'some-random-id-value', + atype: 1, + ext: { + third: 'some-random-id-value' + } + }] + } + ]; + + it(`should send the user id under the extended ids`, function () { + const bidRequestsWithUserId = [{ + bidder: 'openx', + params: { + unit: '11', + delDomain: 'test-del-domain' + }, + userId: { + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bidId: 'test-bid-id-1', + bidderRequestId: 'test-bid-request-1', + auctionId: 'test-auction-1', + userIdAsEids: userIdAsEids + }]; + // enrich bid request with userId key/value + + const request = spec.buildRequests(bidRequestsWithUserId, mockBidderRequest); + expect(request[0].data.user.ext.eids).to.equal(userIdAsEids); + }); + + it(`when no user ids are available, it should not send any extended ids`, function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + expect(request[0].data).to.not.have.any.keys('user'); + }); + }); + }); + + context('banner', function () { + it('should send bid request with a mediaTypes specified with banner type', function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + expect(request[0].data.imp[0]).to.have.any.keys(BANNER); + }); + }); + + context('video', function () { + it('should send bid request with a mediaTypes specified with video type', function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + expect(request[1].data.imp[0]).to.have.any.keys(VIDEO); + }); + }); + + it.skip('should send ad unit ids when any are defined', function () { + const bidRequestsWithUnitIds = [{ + bidder: 'openx', + params: { + delDomain: 'test-del-domain' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bidId: 'test-bid-id-1', + bidderRequestId: 'test-bid-request-1', + auctionId: 'test-auction-1', + transactionId: 'test-transaction-id-1' + }, { + bidder: 'openx', + params: { + unit: '22', + delDomain: 'test-del-domain' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + bidId: 'test-bid-id-2', + bidderRequestId: 'test-bid-request-2', + auctionId: 'test-auction-2', + transactionId: 'test-transaction-id-2' + }]; + mockBidderRequest.bids = bidRequestsWithUnitIds; + const request = spec.buildRequests(bidRequestsWithUnitIds, mockBidderRequest); + expect(request[0].data.imp[1].tagid).to.equal(bidRequestsWithUnitIds[1].params.unit); + expect(request[0].data.imp[1].ext.divid).to.equal(bidRequestsWithUnitIds[1].params.adUnitCode); + }); + }); + + describe('interpretResponse()', function () { + let bidRequestConfigs; + let bidRequest; + let bidResponse; + let bid; + + context('when there is no response', function () { + let bids; + beforeEach(function () { + bidRequestConfigs = [{ + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + }, + }, + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id' + }]; + + bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; + + bidResponse = {nbr: 0}; // Unknown error + bids = spec.interpretResponse({body: bidResponse}, bidRequest); + }); + + it('should not return any bids', function () { + expect(bids.length).to.equal(0); + }); + }); + + context('when there is a response, the common response properties', function () { + beforeEach(function () { + bidRequestConfigs = [{ + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + }, + }, + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id' + }]; + + bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; + + bidResponse = { + seatbid: [{ + bid: [{ + impid: 'test-bid-id', + price: 2, + w: 300, + h: 250, + crid: 'test-creative-id', + dealid: 'test-deal-id', + adm: 'test-ad-markup', + adomain: ['brand.com'], + ext: { + dsp_id: '123', + buyer_id: '456', + brand_id: '789', + paf: { + content_id: 'paf_content_id' + } + } + }] + }], + cur: 'AUS', + ext: { + paf: { + transmission: {version: '12'} + } + } + }; + + bid = spec.interpretResponse({body: bidResponse}, bidRequest)[0]; + }); + + it('should return a price', function () { + expect(bid.cpm).to.equal(bidResponse.seatbid[0].bid[0].price); + }); + + it('should return a request id', function () { + expect(bid.requestId).to.equal(bidResponse.seatbid[0].bid[0].impid); + }); + + it('should return width and height for the creative', function () { + expect(bid.width).to.equal(bidResponse.seatbid[0].bid[0].w); + expect(bid.height).to.equal(bidResponse.seatbid[0].bid[0].h); + }); + + it('should return a creativeId', function () { + expect(bid.creativeId).to.equal(bidResponse.seatbid[0].bid[0].crid); + }); + + it('should return an ad', function () { + expect(bid.ad).to.equal(bidResponse.seatbid[0].bid[0].adm); + }); + + it('should return a deal id if it exists', function () { + expect(bid.dealId).to.equal(bidResponse.seatbid[0].bid[0].dealid); + }); + + it('should have a time-to-live of 5 minutes', function () { + expect(bid.ttl).to.equal(300); + }); + + it('should always return net revenue', function () { + expect(bid.netRevenue).to.equal(true); + }); + + it('should return a currency', function () { + expect(bid.currency).to.equal(bidResponse.cur); + }); + + it('should return a brand ID', function () { + expect(bid.meta.brandId).to.equal(bidResponse.seatbid[0].bid[0].ext.brand_id); + }); + + it('should return a dsp ID', function () { + expect(bid.meta.networkId).to.equal(bidResponse.seatbid[0].bid[0].ext.dsp_id); + }); + + it('should return a buyer ID', function () { + expect(bid.meta.advertiserId).to.equal(bidResponse.seatbid[0].bid[0].ext.buyer_id); + }); + + it('should return adomain', function () { + expect(bid.meta.advertiserDomains).to.equal(bidResponse.seatbid[0].bid[0].adomain); + }); + + it('should return paf fields', function () { + const paf = { + transmission: {version: '12'}, + content_id: 'paf_content_id' + } + expect(bid.meta.paf).to.deep.equal(paf); + }); + }); + + context('when the response is a banner', function() { + beforeEach(function () { + bidRequestConfigs = [{ + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + }, + }, + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id' + }]; + + bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; + + bidResponse = { + seatbid: [{ + bid: [{ + impid: 'test-bid-id', + price: 2, + w: 300, + h: 250, + crid: 'test-creative-id', + dealid: 'test-deal-id', + adm: 'test-ad-markup' + }] + }], + cur: 'AUS' + }; + + bid = spec.interpretResponse({body: bidResponse}, bidRequest)[0]; + }); + + it('should return the proper mediaType', function () { + it('should return a creativeId', function () { + expect(bid.mediaType).to.equal(Object.keys(bidRequestConfigs[0].mediaTypes)[0]); + }); + }); + }); + + context('when the response is a video', function() { + beforeEach(function () { + bidRequestConfigs = [{ + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + video: { + playerSize: [[640, 360], [854, 480]], + }, + }, + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id' + }]; + + bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; + + bidResponse = { + seatbid: [{ + bid: [{ + impid: 'test-bid-id', + price: 2, + w: 854, + h: 480, + crid: 'test-creative-id', + dealid: 'test-deal-id', + adm: 'test-ad-markup', + }] + }], + cur: 'AUS' + }; + }); + + it('should return the proper mediaType', function () { + bid = spec.interpretResponse({body: bidResponse}, bidRequest)[0]; + expect(bid.mediaType).to.equal(Object.keys(bidRequestConfigs[0].mediaTypes)[0]); + }); + + it('should return the proper mediaType', function () { + const winUrl = 'https//my.win.url'; + bidResponse.seatbid[0].bid[0].nurl = winUrl + bid = spec.interpretResponse({body: bidResponse}, bidRequest)[0]; + + expect(bid.vastUrl).to.equal(winUrl); + }); + }); + }); + + describe('user sync', function () { + it('should register the default image pixel if no pixels available', function () { + let syncs = spec.getUserSyncs( + {pixelEnabled: true}, + [] + ); + expect(syncs).to.deep.equal([{type: 'image', url: DEFAULT_SYNC}]); + }); + + it('should register custom syncUrl when exists', function () { + let syncs = spec.getUserSyncs( + {pixelEnabled: true}, + [{body: {ext: {delDomain: 'www.url.com'}}}] + ); + expect(syncs).to.deep.equal([{type: 'image', url: 'https://www.url.com/w/1.0/pd'}]); + }); + + it('should register custom syncUrl when exists', function () { + let syncs = spec.getUserSyncs( + {pixelEnabled: true}, + [{body: {ext: {platform: 'abc'}}}] + ); + expect(syncs).to.deep.equal([{type: 'image', url: SYNC_URL + '?ph=abc'}]); + }); + + it('when iframe sync is allowed, it should register an iframe sync', function () { + let syncs = spec.getUserSyncs( + {iframeEnabled: true}, + [] + ); + expect(syncs).to.deep.equal([{type: 'iframe', url: DEFAULT_SYNC}]); + }); + + it('should prioritize iframe over image for user sync', function () { + let syncs = spec.getUserSyncs( + {iframeEnabled: true, pixelEnabled: true}, + [] + ); + expect(syncs).to.deep.equal([{type: 'iframe', url: DEFAULT_SYNC}]); + }); + + describe('when gdpr applies', function () { + let gdprConsent; + let gdprPixelUrl; + const consentString = 'gdpr-pixel-consent'; + const gdprApplies = '1'; + beforeEach(() => { + gdprConsent = { + consentString, + gdprApplies: true + }; + + gdprPixelUrl = `${SYNC_URL}&gdpr=${gdprApplies}&gdpr_consent=${consentString}`; + }); + + it('when there is a response, it should have the gdpr query params', () => { + let [{url}] = spec.getUserSyncs( + {iframeEnabled: true, pixelEnabled: true}, + [], + gdprConsent + ); + + expect(url).to.have.string(`gdpr_consent=${consentString}`); + expect(url).to.have.string(`gdpr=${gdprApplies}`); + }); + + it('should not send signals if no consent object is available', function () { + let [{url}] = spec.getUserSyncs( + {iframeEnabled: true, pixelEnabled: true}, + [], + ); + expect(url).to.not.have.string('gdpr_consent='); + expect(url).to.not.have.string('gdpr='); + }); + }); + + describe('when ccpa applies', function () { + let usPrivacyConsent; + let uspPixelUrl; + const privacyString = 'TEST'; + beforeEach(() => { + usPrivacyConsent = 'TEST'; + uspPixelUrl = `${DEFAULT_SYNC}&us_privacy=${privacyString}` + }); + it('should send the us privacy string, ', () => { + let [{url}] = spec.getUserSyncs( + {iframeEnabled: true, pixelEnabled: true}, + [], + undefined, + usPrivacyConsent + ); + expect(url).to.have.string(`us_privacy=${privacyString}`); + }); + + it('should not send signals if no consent string is available', function () { + let [{url}] = spec.getUserSyncs( + {iframeEnabled: true, pixelEnabled: true}, + [], + ); + expect(url).to.not.have.string('us_privacy='); + }); + }); + }); +}) +; diff --git a/test/spec/modules/rasBidAdapter_spec.js b/test/spec/modules/rasBidAdapter_spec.js new file mode 100644 index 00000000000..e3f41566c99 --- /dev/null +++ b/test/spec/modules/rasBidAdapter_spec.js @@ -0,0 +1,190 @@ +import { expect } from 'chai'; +import { spec } from 'modules/rasBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; + +const CSR_ENDPOINT = 'https://csr.onet.pl/4178463/csr-006/csr.json?'; + +describe('rasBidAdapter', function () { + const adapter = newBidder(spec); + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + it('should return true when required params found', function () { + const bid = { + sizes: [[300, 250], [300, 600]], + bidder: 'ras', + params: { + slot: 'slot', + area: 'areatest', + site: 'test', + network: '4178463' + } + }; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params not found', function () { + const failBid = { + sizes: [[300, 250], [300, 300]], + bidder: 'ras', + params: { + site: 'test', + network: '4178463' + } + }; + expect(spec.isBidRequestValid(failBid)).to.equal(false); + }); + + it('should return nothing when bid request is malformed', function () { + const failBid = { + sizes: [[300, 250], [300, 300]], + bidder: 'ras', + }; + expect(spec.isBidRequestValid(failBid)).to.equal(undefined); + }); + }); + + describe('buildRequests', function () { + const bid = { + sizes: [[300, 250], [300, 600]], + bidder: 'ras', + bidId: 1, + params: { + slot: 'test', + area: 'areatest', + site: 'test', + network: '4178463' + } + }; + const bid2 = { + sizes: [[750, 300]], + bidder: 'ras', + bidId: 2, + params: { + slot: 'test2', + area: 'areatest', + site: 'test', + network: '4178463' + } + }; + + it('should parse bids to request', function () { + const requests = spec.buildRequests([bid], { + 'gdprConsent': { + 'gdprApplies': true, + 'consentString': 'some-consent-string' + }, + 'refererInfo': { + 'ref': 'https://example.org/', + 'page': 'https://example.com/' + } + }); + expect(requests[0].url).to.have.string(CSR_ENDPOINT); + expect(requests[0].url).to.have.string('slot0=test'); + expect(requests[0].url).to.have.string('id0=1'); + expect(requests[0].url).to.have.string('site=test'); + expect(requests[0].url).to.have.string('area=areatest'); + expect(requests[0].url).to.have.string('cre_format=html'); + expect(requests[0].url).to.have.string('systems=das'); + expect(requests[0].url).to.have.string('ems_url=1'); + expect(requests[0].url).to.have.string('bid_rate=1'); + expect(requests[0].url).to.have.string('gdpr_applies=true'); + expect(requests[0].url).to.have.string('euconsent=some-consent-string'); + expect(requests[0].url).to.have.string('du=https%3A%2F%2Fexample.com%2F'); + expect(requests[0].url).to.have.string('dr=https%3A%2F%2Fexample.org%2F'); + }); + + it('should return empty consent string when undefined', function () { + const requests = spec.buildRequests([bid]); + const gdpr = requests[0].url.search('gdpr_applies'); + const euconsent = requests[0].url.search('euconsent='); + expect(gdpr).to.equal(-1); + expect(euconsent).to.equal(-1); + }); + + it('should parse bids to request from pageContext', function () { + const bidCopy = { ...bid }; + bidCopy.params = { + ...bid.params, + pageContext: { + dv: 'test/areatest', + du: 'https://example.com/', + dr: 'https://example.org/', + keyWords: ['val1', 'val2'], + keyValues: { + adunit: 'test/areatest' + } + } + }; + const requests = spec.buildRequests([bidCopy, bid2]); + expect(requests[0].url).to.have.string(CSR_ENDPOINT); + expect(requests[0].url).to.have.string('slot0=test'); + expect(requests[0].url).to.have.string('id0=1'); + expect(requests[0].url).to.have.string('iusizes0=300x250%2C300x600'); + expect(requests[0].url).to.have.string('slot1=test2'); + expect(requests[0].url).to.have.string('id1=2'); + expect(requests[0].url).to.have.string('iusizes1=750x300'); + expect(requests[0].url).to.have.string('site=test'); + expect(requests[0].url).to.have.string('area=areatest'); + expect(requests[0].url).to.have.string('cre_format=html'); + expect(requests[0].url).to.have.string('systems=das'); + expect(requests[0].url).to.have.string('ems_url=1'); + expect(requests[0].url).to.have.string('bid_rate=1'); + expect(requests[0].url).to.have.string('du=https%3A%2F%2Fexample.com%2F'); + expect(requests[0].url).to.have.string('dr=https%3A%2F%2Fexample.org%2F'); + expect(requests[0].url).to.have.string('DV=test%2Fareatest'); + expect(requests[0].url).to.have.string('kwrd=val1%2Bval2'); + expect(requests[0].url).to.have.string('kvadunit=test%2Fareatest'); + }); + }); + + describe('interpretResponse', function () { + const response = { + 'adsCheck': 'ok', + 'geoloc': {}, + 'ir': '92effd60-0c84-4dac-817e-763ea7b8ac65', + 'ads': [ + { + 'id': 'flat-belkagorna', + 'slot': 'flat-belkagorna', + 'prio': 10, + 'type': 'html', + 'bid_rate': 0.321123, + 'adid': 'das,50463,152276', + 'id_3': '12734', + 'html': '' + } + ], + 'iv': '202003191334467636346500' + }; + + it('should get correct bid response', function () { + const resp = spec.interpretResponse({ body: response }, { bidIds: [{ slot: 'flat-belkagorna', bidId: 1 }] }); + expect(resp[0]).to.have.all.keys('cpm', 'currency', 'netRevenue', 'requestId', 'ttl', 'width', 'height', 'creativeId', 'dealId', 'ad', 'meta'); + expect(resp.length).to.equal(1); + }); + + it('should handle empty ad', function () { + let res = { + 'ads': [{ + type: 'empty' + }] + }; + const resp = spec.interpretResponse({ body: res }, {}); + expect(resp).to.deep.equal([]); + }); + + it('should handle empty server response', function () { + let res = { + 'ads': [] + }; + const resp = spec.interpretResponse({ body: res }, {}); + expect(resp).to.deep.equal([]); + }); + }); +}); diff --git a/test/spec/modules/shinezBidAdapter_spec.js b/test/spec/modules/shinezBidAdapter_spec.js new file mode 100644 index 00000000000..4e6c2d3420e --- /dev/null +++ b/test/spec/modules/shinezBidAdapter_spec.js @@ -0,0 +1,479 @@ +import { expect } from 'chai'; +import { spec } from 'modules/shinezBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import { config } from 'src/config.js'; +import { BANNER, VIDEO } from '../../../src/mediaTypes.js'; +import * as utils from 'src/utils.js'; + +const ENDPOINT = 'https://hb.sweetgum.io/hb-sz-multi'; +const TEST_ENDPOINT = 'https://hb.sweetgum.io/hb-multi-sz-test'; +const TTL = 360; +/* eslint no-console: ["error", { allow: ["log", "warn", "error"] }] */ + +describe('shinezAdapter', function () { + const adapter = newBidder(spec); + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + const bid = { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [['640', '480']], + 'params': { + 'org': 'jdye8weeyirk00000001' + } + }; + + it('should return true when required params are passed', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not found', function () { + const newBid = Object.assign({}, bid); + delete newBid.params; + newBid.params = { + 'org': null + }; + expect(spec.isBidRequestValid(newBid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + const bidRequests = [ + { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [[640, 480]], + 'params': { + 'org': 'jdye8weeyirk00000001' + }, + 'bidId': '299ffc8cca0b87', + 'bidderRequestId': '1144f487e563f9', + 'auctionId': 'bfc420c3-8577-4568-9766-a8a935fb620d', + 'mediaTypes': { + 'video': { + 'playerSize': [[640, 480]], + 'context': 'instream' + } + }, + 'vastXml': '"..."' + }, + { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250]], + 'params': { + 'org': 'jdye8weeyirk00000001' + }, + 'bidId': '299ffc8cca0b87', + 'bidderRequestId': '1144f487e563f9', + 'auctionId': 'bfc420c3-8577-4568-9766-a8a935fb620d', + 'mediaTypes': { + 'banner': { + } + }, + 'ad': '""' + } + ]; + + const testModeBidRequests = [ + { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [[640, 480]], + 'params': { + 'org': 'jdye8weeyirk00000001', + 'testMode': true + }, + 'bidId': '299ffc8cca0b87', + 'bidderRequestId': '1144f487e563f9', + 'auctionId': 'bfc420c3-8577-4568-9766-a8a935fb620d', + } + ]; + + const bidderRequest = { + bidderCode: 'shinez', + } + const placementId = '12345678'; + + it('sends the placementId to ENDPOINT via POST', function () { + bidRequests[0].params.placementId = placementId; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bids[0].placementId).to.equal(placementId); + }); + + it('sends bid request to ENDPOINT via POST', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.url).to.equal(ENDPOINT); + expect(request.method).to.equal('POST'); + }); + + it('sends bid request to TEST ENDPOINT via POST', function () { + const request = spec.buildRequests(testModeBidRequests, bidderRequest); + expect(request.url).to.equal(TEST_ENDPOINT); + expect(request.method).to.equal('POST'); + }); + + it('should send the correct bid Id', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bids[0].bidId).to.equal('299ffc8cca0b87'); + }); + + it('should send the correct sizes array', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bids[0].sizes).to.be.an('array'); + expect(request.data.bids[0].sizes).to.equal(bidRequests[0].sizes) + expect(request.data.bids[1].sizes).to.be.an('array'); + expect(request.data.bids[1].sizes).to.equal(bidRequests[1].sizes) + }); + + it('should send the correct media type', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bids[0].mediaType).to.equal(VIDEO) + expect(request.data.bids[1].mediaType).to.equal(BANNER) + }); + + it('should respect syncEnabled option', function() { + config.setConfig({ + userSync: { + syncEnabled: false, + filterSettings: { + all: { + bidders: '*', + filter: 'include' + } + } + } + }); + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.not.have.property('cs_method'); + }); + + it('should respect "iframe" filter settings', function () { + config.setConfig({ + userSync: { + syncEnabled: true, + filterSettings: { + iframe: { + bidders: [spec.code], + filter: 'include' + } + } + } + }); + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.have.property('cs_method', 'iframe'); + }); + + it('should respect "all" filter settings', function () { + config.setConfig({ + userSync: { + syncEnabled: true, + filterSettings: { + all: { + bidders: [spec.code], + filter: 'include' + } + } + } + }); + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.have.property('cs_method', 'iframe'); + }); + + it('should send the pixel user sync param if userSync is enabled and no "iframe" or "all" configs are present', function () { + config.resetConfig(); + config.setConfig({ + userSync: { + syncEnabled: true, + } + }); + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.have.property('cs_method', 'pixel'); + }); + + it('should respect total exclusion', function() { + config.setConfig({ + userSync: { + syncEnabled: true, + filterSettings: { + image: { + bidders: [spec.code], + filter: 'exclude' + }, + iframe: { + bidders: [spec.code], + filter: 'exclude' + } + } + } + }); + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.not.have.property('cs_method'); + }); + + it('should have us_privacy param if usPrivacy is available in the bidRequest', function () { + const bidderRequestWithUSP = Object.assign({uspConsent: '1YNN'}, bidderRequest); + const request = spec.buildRequests(bidRequests, bidderRequestWithUSP); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.have.property('us_privacy', '1YNN'); + }); + + it('should have an empty us_privacy param if usPrivacy is missing in the bidRequest', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.not.have.property('us_privacy'); + }); + + it('should not send the gdpr param if gdprApplies is false in the bidRequest', function () { + const bidderRequestWithGDPR = Object.assign({gdprConsent: {gdprApplies: false}}, bidderRequest); + const request = spec.buildRequests(bidRequests, bidderRequestWithGDPR); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.not.have.property('gdpr'); + expect(request.data.params).to.not.have.property('gdpr_consent'); + }); + + it('should send the gdpr param if gdprApplies is true in the bidRequest', function () { + const bidderRequestWithGDPR = Object.assign({gdprConsent: {gdprApplies: true, consentString: 'test-consent-string'}}, bidderRequest); + const request = spec.buildRequests(bidRequests, bidderRequestWithGDPR); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.have.property('gdpr', true); + expect(request.data.params).to.have.property('gdpr_consent', 'test-consent-string'); + }); + + it('should have schain param if it is available in the bidRequest', () => { + const schain = { + ver: '1.0', + complete: 1, + nodes: [{ asi: 'indirectseller.com', sid: '00001', hp: 1 }], + }; + bidRequests[0].schain = schain; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.have.property('schain', '1.0,1!indirectseller.com,00001,1,,,'); + }); + + it('should set flooPrice to getFloor.floor value if it is greater than params.floorPrice', function() { + const bid = utils.deepClone(bidRequests[0]); + bid.getFloor = () => { + return { + currency: 'USD', + floor: 3.32 + } + } + bid.params.floorPrice = 0.64; + const request = spec.buildRequests([bid], bidderRequest); + expect(request.data.bids[0]).to.be.an('object'); + expect(request.data.bids[0]).to.have.property('floorPrice', 3.32); + }); + + it('should set floorPrice to params.floorPrice value if it is greater than getFloor.floor', function() { + const bid = utils.deepClone(bidRequests[0]); + bid.getFloor = () => { + return { + currency: 'USD', + floor: 0.8 + } + } + bid.params.floorPrice = 1.5; + const request = spec.buildRequests([bid], bidderRequest); + expect(request.data.bids[0]).to.be.an('object'); + expect(request.data.bids[0]).to.have.property('floorPrice', 1.5); + }); + }); + + describe('interpretResponse', function () { + const response = { + params: { + currency: 'USD', + netRevenue: true, + }, + bids: [{ + cpm: 12.5, + vastXml: '', + width: 640, + height: 480, + requestId: '21e12606d47ba7', + adomain: ['abc.com'], + mediaType: VIDEO + }, + { + cpm: 12.5, + ad: '""', + width: 300, + height: 250, + requestId: '21e12606d47ba7', + adomain: ['abc.com'], + mediaType: BANNER + }] + }; + + const expectedVideoResponse = { + requestId: '21e12606d47ba7', + cpm: 12.5, + currency: 'USD', + width: 640, + height: 480, + ttl: TTL, + creativeId: '21e12606d47ba7', + netRevenue: true, + nurl: 'http://example.com/win/1234', + mediaType: VIDEO, + meta: { + mediaType: VIDEO, + advertiserDomains: ['abc.com'] + }, + vastXml: '', + }; + + const expectedBannerResponse = { + requestId: '21e12606d47ba7', + cpm: 12.5, + currency: 'USD', + width: 640, + height: 480, + ttl: TTL, + creativeId: '21e12606d47ba7', + netRevenue: true, + nurl: 'http://example.com/win/1234', + mediaType: BANNER, + meta: { + mediaType: BANNER, + advertiserDomains: ['abc.com'] + }, + ad: '""' + }; + + it('should get correct bid response', function () { + const result = spec.interpretResponse({ body: response }); + expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedVideoResponse)); + expect(Object.keys(result[1])).to.deep.equal(Object.keys(expectedBannerResponse)); + }); + + it('video type should have vastXml key', function () { + const result = spec.interpretResponse({ body: response }); + expect(result[0].vastXml).to.equal(expectedVideoResponse.vastXml) + }); + + it('banner type should have ad key', function () { + const result = spec.interpretResponse({ body: response }); + expect(result[1].ad).to.equal(expectedBannerResponse.ad) + }); + }) + + describe('getUserSyncs', function() { + const imageSyncResponse = { + body: { + params: { + userSyncPixels: [ + 'https://image-sync-url.test/1', + 'https://image-sync-url.test/2', + 'https://image-sync-url.test/3' + ] + } + } + }; + + const iframeSyncResponse = { + body: { + params: { + userSyncURL: 'https://iframe-sync-url.test' + } + } + }; + + it('should register all img urls from the response', function() { + const syncs = spec.getUserSyncs({ pixelEnabled: true }, [imageSyncResponse]); + expect(syncs).to.deep.equal([ + { + type: 'image', + url: 'https://image-sync-url.test/1' + }, + { + type: 'image', + url: 'https://image-sync-url.test/2' + }, + { + type: 'image', + url: 'https://image-sync-url.test/3' + } + ]); + }); + + it('should register the iframe url from the response', function() { + const syncs = spec.getUserSyncs({ iframeEnabled: true }, [iframeSyncResponse]); + expect(syncs).to.deep.equal([ + { + type: 'iframe', + url: 'https://iframe-sync-url.test' + } + ]); + }); + + it('should register both image and iframe urls from the responses', function() { + const syncs = spec.getUserSyncs({ pixelEnabled: true, iframeEnabled: true }, [iframeSyncResponse, imageSyncResponse]); + expect(syncs).to.deep.equal([ + { + type: 'iframe', + url: 'https://iframe-sync-url.test' + }, + { + type: 'image', + url: 'https://image-sync-url.test/1' + }, + { + type: 'image', + url: 'https://image-sync-url.test/2' + }, + { + type: 'image', + url: 'https://image-sync-url.test/3' + } + ]); + }); + + it('should handle an empty response', function() { + const syncs = spec.getUserSyncs({ iframeEnabled: true }, []); + expect(syncs).to.deep.equal([]); + }); + + it('should handle when user syncs are disabled', function() { + const syncs = spec.getUserSyncs({ pixelEnabled: false }, [imageSyncResponse]); + expect(syncs).to.deep.equal([]); + }); + }) + + describe('onBidWon', function() { + beforeEach(function() { + sinon.stub(utils, 'triggerPixel'); + }); + afterEach(function() { + utils.triggerPixel.restore(); + }); + + it('Should trigger pixel if bid nurl', function() { + const bid = { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [['640', '480']], + 'nurl': 'http://example.com/win/1234', + 'params': { + 'org': 'jdye8weeyirk00000001' + } + }; + + spec.onBidWon(bid); + expect(utils.triggerPixel.callCount).to.equal(1) + }) + }) +}); From 935dbd46c42c4c90fdc5903a384abe290f152bdd Mon Sep 17 00:00:00 2001 From: Gino Cirlini Date: Thu, 21 Jul 2022 11:54:06 +0200 Subject: [PATCH 29/29] update smartclip outstream player version to support outstream 6 release along with necessary config changes --- modules/smartxBidAdapter.js | 58 ++++++++++++---------- test/spec/modules/smartxBidAdapter_spec.js | 6 +-- 2 files changed, 36 insertions(+), 28 deletions(-) diff --git a/modules/smartxBidAdapter.js b/modules/smartxBidAdapter.js index 5b36e17be11..f3188c1f110 100644 --- a/modules/smartxBidAdapter.js +++ b/modules/smartxBidAdapter.js @@ -303,7 +303,7 @@ export const spec = { const playersize = deepAccess(currentBidRequest, 'mediaTypes.video.playerSize'); const renderer = Renderer.install({ id: 0, - url: 'https://dco.smartclip.net/?plc=7777778', + url: 'https://dco.smartclip.net/?plc=7777779', config: { adText: 'SmartX Outstream Video Ad via Prebid.js', player_width: playersize[0][0], @@ -353,65 +353,73 @@ function createOutstreamConfig(bid) { logMessage('[SMARTX][renderer] Handle SmartX outstream renderer'); - var smartPlayObj = { + var playerConfig = { minAdWidth: confMinAdWidth, maxAdWidth: confMaxAdWidth, - onStartCallback: function (m, n) { + coreSetup: {}, + layoutSettings: {}, + onCappedCallback: function() { try { - window.sc_smartIntxtStart(n); - } catch (f) {} - }, - onCappedCallback: function (m, n) { - try { - window.sc_smartIntxtNoad(n); - } catch (f) {} - }, - onEndCallback: function (m, n) { - try { - window.sc_smartIntxtEnd(n); + window.sc_smartIntxtNoad(); } catch (f) {} }, }; if (confStartOpen == 'true') { - smartPlayObj.startOpen = true; + playerConfig.startOpen = true; } else if (confStartOpen == 'false') { - smartPlayObj.startOpen = false; + playerConfig.startOpen = false; } if (confEndingScreen == 'true') { - smartPlayObj.endingScreen = true; + playerConfig.endingScreen = true; } else if (confEndingScreen == 'false') { - smartPlayObj.endingScreen = false; + playerConfig.endingScreen = false; } if (confTitle || (typeof bid.renderer.config.outstream_options.title == 'string' && bid.renderer.config.outstream_options.title == '')) { - smartPlayObj.title = confTitle; + playerConfig.layoutSettings.advertisingLabel = confTitle; } if (confSkipOffset) { - smartPlayObj.skipOffset = confSkipOffset; + playerConfig.coreSetup.skipOffset = confSkipOffset; } if (confDesiredBitrate) { - smartPlayObj.desiredBitrate = confDesiredBitrate; + playerConfig.coreSetup.desiredBitrate = confDesiredBitrate; } if (confVisibilityThreshold) { - smartPlayObj.visibilityThreshold = confVisibilityThreshold; + playerConfig.visibilityThreshold = confVisibilityThreshold; } - smartPlayObj.adResponse = bid.vastContent; + playerConfig.adResponse = bid.vastContent; const divID = '[id="' + elementId + '"]'; + var playerListener = function callback(event) { + switch (event) { + case 'AdSlotStarted': + try { + window.sc_smartIntxtStart(); + } catch (f) {} + break; + + case 'AdSlotComplete': + try { + window.sc_smartIntxtEnd(); + } catch (f) {} + break; + } + }; + try { // eslint-disable-next-line - let _outstreamPlayer = new OutstreamPlayer(divID, smartPlayObj); + outstreamplayer.connect(divID).setup(playerConfig, playerListener) } catch (e) { logError('[SMARTX][renderer] Error caught: ' + e); } - return smartPlayObj; + return playerConfig; } /** diff --git a/test/spec/modules/smartxBidAdapter_spec.js b/test/spec/modules/smartxBidAdapter_spec.js index b7235eee878..58ce50efb8e 100644 --- a/test/spec/modules/smartxBidAdapter_spec.js +++ b/test/spec/modules/smartxBidAdapter_spec.js @@ -514,7 +514,7 @@ describe('The smartx adapter', function () { responses[0].renderer.render(responses[0]); - expect(responses[0].renderer.url).to.equal('https://dco.smartclip.net/?plc=7777778'); + expect(responses[0].renderer.url).to.equal('https://dco.smartclip.net/?plc=7777779'); window.document.getElementById.restore(); }); @@ -542,7 +542,7 @@ describe('The smartx adapter', function () { responses[0].renderer.render(responses[0]); - expect(responses[0].renderer.url).to.equal('https://dco.smartclip.net/?plc=7777778'); + expect(responses[0].renderer.url).to.equal('https://dco.smartclip.net/?plc=7777779'); window.document.getElementById.restore(); }); @@ -560,7 +560,7 @@ describe('The smartx adapter', function () { responses[0].renderer.render(responses[0]); - expect(responses[0].renderer.url).to.equal('https://dco.smartclip.net/?plc=7777778'); + expect(responses[0].renderer.url).to.equal('https://dco.smartclip.net/?plc=7777779'); window.document.getElementById.restore(); });