diff --git a/pr_review.md b/PR_REVIEW.md similarity index 52% rename from pr_review.md rename to PR_REVIEW.md index 1d8cf0c360c..80d11c35769 100644 --- a/pr_review.md +++ b/PR_REVIEW.md @@ -1,5 +1,9 @@ ## Summary -We take PR review seriously. Please read https://medium.com/@mrjoelkemp/giving-better-code-reviews-16109e0fdd36#.xa8lc4i23 to understand how a PR review should be conducted. Be rational and strict in your review, make sure you understand exactly what the submitter's intent is. Overall 1 person should take ownership of a particular PR. When they are satisfied it's in good condition to merge, they should request 1 additional team member to review as a sanity check. Only when the PR has 2 `LGTM` from the core team should it be merged. +We take PR review seriously. Please read https://medium.com/@mrjoelkemp/giving-better-code-reviews-16109e0fdd36#.xa8lc4i23 to understand how a PR review should be conducted. Be rational and strict in your review, make sure you understand exactly what the submitter's intent is. Anyone in the community can review a PR, but a Prebid Org member is also required. A Prebid Org member should take ownership of a PR and do the initial review. + +If the PR is for a standard bid adapter or a standard analytics adapter, just the one review from a core member is sufficient. The reviewer will check against [required conventions](http://prebid.org/dev-docs/bidder-adaptor.html#required-adapter-conventions) and may merge the PR after approving and confirming that the documentation PR against prebid.org is open and linked to the issue. + +For modules and core platform updates, the initial reviewer should request an additional team member to review as a sanity check. Merge should only happen when the PR has 2 `LGTM` from the core team and a documentation PR if required. ### General PR review Process - Checkout the branch (these instructions are available on the github PR page as well). @@ -13,7 +17,7 @@ We take PR review seriously. Please read https://medium.com/@mrjoelkemp/giving-b - If all above is good, add a `LGTM` comment and request 1 additional core member to review. - Once there is 2 `LGTM` on the PR, merge to master - Ask the submitter to add a PR for documentation if applicable. -- Add a line into the `draft release` notes for this submission. If no draft release is available, create one using this template https://gist.github.com/mkendall07/c3af6f4691bed8a46738b3675cb5a479 +- Add a line into the `draft release` notes for this submission. If no draft release is available, create one using [this template]( https://gist.github.com/mkendall07/c3af6f4691bed8a46738b3675cb5a479) ### New Adapter or updates to adapter process - Follow steps above for general review process. In addition, please verify the following: @@ -23,3 +27,20 @@ We take PR review seriously. Please read https://medium.com/@mrjoelkemp/giving-b - Verify that code re-use is being done properly and that changes introduced by a bidder don't impact other bidders. - If the adapter being submitted is an alias type, check with the bidder contact that is being aliased to make sure it's allowed. - If the adapter is triggering any user syncs make sure they are using the user sync module in the Prebid.js core. +- Requests to the bidder should support HTTPS +- Responses from the bidder should be compressed (such as gzip, compress, deflate) +- Bid responses may not use JSONP: All requests must be AJAX with JSON responses +- All user-sync (aka pixel) activity must be registered via the provided functions +- Adapters may not use the $$PREBID_GLOBAL$$ variable +- All adapters must support the creation of multiple concurrent instances. This means, for example, that adapters cannot rely on mutable global variables. + +## Ticket Coordinator + +Each week, Prebid Org assigns one person to keep an eye on incoming issues and PRs. That person should: +- Review issues and PRs at least once per weekday for new items. +- For PRs: assign PRs to individuals on the PR review list. Try to be equitable -- not all PRs are created equally. Use the "Assigned" field and add the "Needs Review" label. +- For Issues: try to address questions and troubleshooting requests on your own, assigning them to others as needed. +- Issues that are questions or troubleshooting requests may be closed if the originator doesn't respond within a week to requests for confirmation or details. +- Issues that are bug reports should be left open and assigned to someone in PR rotation to confirm or deny the bug status. +- It's polite to check with others before assigning them large tasks. +- If possible, check in on older items and see if they can be unstuck. diff --git a/gulpfile.js b/gulpfile.js index 92851bbc192..0fb4a7e1de0 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -71,12 +71,17 @@ function nodeBundle(modules) { }); } +// these modules must be explicitly listed in --modules to be included in the build, won't be part of "all" modules +var explicitModules = [ + 'pre1api' +]; + function bundle(dev, moduleArr) { var modules = moduleArr || helpers.getArgModules(), allModules = helpers.getModuleNames(modules); if(modules.length === 0) { - modules = allModules; + modules = allModules.filter(module => !explicitModules.includes(module)); } else { var diff = _.difference(modules, allModules); if(diff.length !== 0) { diff --git a/integrationExamples/gpt/serverbidServer_example.html b/integrationExamples/gpt/serverbidServer_example.html new file mode 100644 index 00000000000..3d76e963663 --- /dev/null +++ b/integrationExamples/gpt/serverbidServer_example.html @@ -0,0 +1,103 @@ + + + + + + + + +

Prebid.js S2S Example

+ +
Div-1
+
+ +
+ + diff --git a/modules/33acrossBidAdapter.js b/modules/33acrossBidAdapter.js index 8e73d5c87b4..5a7fa5e2613 100644 --- a/modules/33acrossBidAdapter.js +++ b/modules/33acrossBidAdapter.js @@ -16,7 +16,7 @@ function _createBidResponse(response) { height: response.seatbid[0].bid[0].h, ad: response.seatbid[0].bid[0].adm, ttl: response.seatbid[0].bid[0].ttl || 60, - creativeId: response.seatbid[0].bid[0].ext.rp.advid, + creativeId: response.seatbid[0].bid[0].crid, currency: response.cur, netRevenue: true } @@ -27,6 +27,9 @@ function _createServerRequest(bidRequest) { const ttxRequest = {}; const params = bidRequest.params; + /* + * Infer data for the request payload + */ ttxRequest.imp = []; ttxRequest.imp[0] = { banner: { @@ -38,22 +41,27 @@ function _createServerRequest(bidRequest) { } } } - ttxRequest.site = { id: params.siteId }; - // Go ahead send the bidId in request to 33exchange so it's kept track of in the bid response and // therefore in ad targetting process ttxRequest.id = bidRequest.bidId; + // Finally, set the openRTB 'test' param if this is to be a test bid + if (params.test === 1) { + ttxRequest.test = 1; + } + /* + * Now construt the full server request + */ const options = { contentType: 'application/json', withCredentials: false }; - // Allow the ability to configure the HB endpoint for testing purposes. const ttxSettings = config.getConfig('ttxSettings'); const url = (ttxSettings && ttxSettings.url) || END_POINT; + // Return the server request return { 'method': 'POST', 'url': url, @@ -93,12 +101,7 @@ function isBidRequestValid(bid) { return false; } - if ((typeof bid.params.site === 'undefined' || typeof bid.params.site.id === 'undefined') && - (typeof bid.params.siteId === 'undefined')) { - return false; - } - - if (typeof bid.params.productId === 'undefined') { + if (typeof bid.params.siteId === 'undefined' || typeof bid.params.productId === 'undefined') { return false; } diff --git a/modules/adformBidAdapter.js b/modules/adformBidAdapter.js index 6dba55f88a7..4ff37566da9 100644 --- a/modules/adformBidAdapter.js +++ b/modules/adformBidAdapter.js @@ -5,7 +5,6 @@ import {registerBidder} from 'src/adapters/bidderFactory'; const BIDDER_CODE = 'adform'; export const spec = { code: BIDDER_CODE, - supportedMediaTypes: [], isBidRequestValid: function (bid) { return !!(bid.params.mid); }, @@ -35,7 +34,7 @@ export const spec = { request.unshift('//' + globalParams[0][1] + '/adx/?rp=4'); - request.push('stid=' + validBidRequests[0].requestId); + request.push('stid=' + validBidRequests[0].auctionId); for (i = 1, l = globalParams.length; i < l; i++) { _key = globalParams[i][0]; diff --git a/modules/adgenerationBidAdapter.js b/modules/adgenerationBidAdapter.js new file mode 100644 index 00000000000..8635c684af7 --- /dev/null +++ b/modules/adgenerationBidAdapter.js @@ -0,0 +1,196 @@ +import * as utils from 'src/utils'; +// import {config} from 'src/config'; +import {registerBidder} from 'src/adapters/bidderFactory'; +import { BANNER, NATIVE } from 'src/mediaTypes'; +const ADG_BIDDER_CODE = 'adgeneration'; + +export const spec = { + code: ADG_BIDDER_CODE, + aliases: ['adg'], // short code + supportedMediaTypes: [BANNER, NATIVE], + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function (bid) { + return !!(bid.params.id); + }, + /** + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function (validBidRequests) { + let serverRequests = []; + for (let i = 0, len = validBidRequests.length; i < len; i++) { + const validReq = validBidRequests[i]; + const DEBUG_URL = 'http://api-test.scaleout.jp/adsv/v1'; + const URL = 'https://d.socdm.com/adsv/v1'; + const url = validReq.params.debug ? DEBUG_URL : URL; + let data = ``; + data = utils.tryAppendQueryString(data, 'posall', 'SSPLOC'); + const id = utils.getBidIdParameter('id', validReq.params); + data = utils.tryAppendQueryString(data, 'id', id); + data = utils.tryAppendQueryString(data, 'sdktype', '0'); + data = utils.tryAppendQueryString(data, 'hb', 'true'); + data = utils.tryAppendQueryString(data, 't', 'json3'); + data = utils.tryAppendQueryString(data, 'transactionid', validReq.transactionId); + + // native以外にvideo等の対応が入った場合は要修正 + if (!validReq.mediaTypes || !validReq.mediaTypes.native) { + data = utils.tryAppendQueryString(data, 'imark', '1'); + } + + // remove the trailing "&" + if (data.lastIndexOf('&') === data.length - 1) { + data = data.substring(0, data.length - 1); + } + serverRequests.push({ + method: 'GET', + url: url, + data: data, + bidRequest: validBidRequests[i] + }); + } + return serverRequests; + }, + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @param {BidRequest} bidRequest + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse, bidRequests) { + const body = serverResponse.body; + const bidResponses = []; + let bidRequest = {}; + if (body.results && body.results.length > 0) { + bidRequest = bidRequests.bidRequest; + const bidResponse = { + requestId: bidRequest.bidId, + cpm: body.cpm || 0, + width: bidRequest.sizes[0][0], + height: bidRequest.sizes[0][1], + creativeId: body.creativeid || '', + dealId: body.dealid || '', + currency: 'JPY', + netRevenue: true, + ttl: body.ttl || 10, + referrer: utils.getTopWindowUrl(), + }; + if (bidRequest.mediaTypes && bidRequest.mediaTypes.native) { + bidResponse.native = createNativeAd(body); + bidResponse.mediaType = NATIVE; + } else { + // banner + bidResponse.ad = createAd(body, bidRequest); + } + bidResponses.push(bidResponse); + } + return bidResponses; // noAdは空を返す + }, + + /** + * Register the user sync pixels which should be dropped after the auction. + * + * @param {SyncOptions} syncOptions Which user syncs are allowed? + * @param {ServerResponse[]} serverResponses List of server's responses. + * @return {UserSync[]} The user syncs which should be dropped. + */ + getUserSyncs: function (syncOptions, serverResponses) { + const syncs = []; + return syncs; + } +}; + +function createAd(body, bidRequest) { + let ad = body.ad; + if (body.vastxml && body.vastxml.length > 0) { + ad = `
` + + appendChildToBody(ad, createAPVTag() + insertVASTMethod(bidRequest.bidId, body.vastxml)); + } + ad = appendChildToBody(ad, body.beacon); + if (removeWrapper(ad)) return removeWrapper(ad); + return ad; +} + +function createNativeAd(body) { + let native = {}; + if (body.native_ad && body.native_ad.assets.length > 0) { + const assets = body.native_ad.assets; + for (let i = 0, len = assets.length; i < len; i++) { + switch (assets[i].id) { + case 1: + native.title = assets[i].title.text; + break; + case 2: + native.image = { + url: assets[i].img.url, + height: assets[i].img.h, + width: assets[i].img.w, + }; + break; + case 3: + native.icon = { + url: assets[i].img.url, + height: assets[i].img.h, + width: assets[i].img.w, + }; + break; + case 4: + native.sponsoredBy = assets[i].data.value; + break; + case 5: + native.body = assets[i].data.value; + break; + case 6: + native.cta = assets[i].data.value; + break; + } + } + native.clickUrl = body.native_ad.link.url; + native.clickTrackers = body.native_ad.link.clicktrackers || []; + native.impressionTrackers = body.native_ad.imptrackers || []; + } + return native; +} + +function appendChildToBody(ad, data) { + return ad.replace(/<\/\s?body>/, `${data}`); +} + +function createAPVTag() { + const APVURL = 'https://cdn.apvdr.com/js/VideoAd.min.js'; + let apvScript = document.createElement('script'); + apvScript.type = 'text/javascript'; + apvScript.id = 'apv'; + apvScript.src = APVURL; + return apvScript.outerHTML; +} + +function insertVASTMethod(targetId, vastXml) { + let apvVideoAdParam = { + s: targetId + }; + let script = document.createElement(`script`); + script.type = 'text/javascript'; + script.innerHTML = `(function(){ new APV.VideoAd(${JSON.stringify(apvVideoAdParam)}).load('${vastXml.replace(/\r?\n/g, '')}'); })();`; + return script.outerHTML; +} + +/** + * + * @param ad + */ +function removeWrapper(ad) { + const bodyIndex = ad.indexOf(''); + const lastBodyIndex = ad.lastIndexOf(''); + if (bodyIndex === -1 || lastBodyIndex === -1) return false; + return ad.substr(bodyIndex, lastBodyIndex).replace('', '').replace('', ''); +} + +registerBidder(spec); diff --git a/modules/adgenerationBidAdapter.md b/modules/adgenerationBidAdapter.md new file mode 100644 index 00000000000..d694b376ff9 --- /dev/null +++ b/modules/adgenerationBidAdapter.md @@ -0,0 +1,68 @@ +# Overview + +``` +Module Name: Adgeneration Bid Adapter +Module Type: Bidder Adapter +Maintainer: ssp-ope@supership.jp +``` + +# Description + +Connects to Adgeneration exchange for bids. + +Adgeneration bid adapter supports Banner and Native. + +# Test Parameters +``` +var adUnits = [ + // Banner adUnit + { + code: 'banner-div', // banner + sizes: [[300, 250]], + bids: [ + { + bidder: 'adg', + params: { + id: '58278', // banner + } + }, + ] + }, + // Native adUnit + { + code: 'native-div', + sizes: [[1,1]], + mediaTypes: { + native: { + image: { + required: true + }, + title: { + required: true, + len: 80 + }, + sponsoredBy: { + required: true + }, + clickUrl: { + required: true + }, + body: { + required: true + }, + icon: { + required: true + } + }, + }, + bids: [ + { + bidder: 'adg', + params: { + id: '58279', //native + } + }, + ] + }, +]; +``` diff --git a/modules/adkernelBidAdapter.js b/modules/adkernelBidAdapter.js index c3f34274692..14cd7f70262 100644 --- a/modules/adkernelBidAdapter.js +++ b/modules/adkernelBidAdapter.js @@ -7,7 +7,7 @@ import includes from 'core-js/library/fn/array/includes'; const VIDEO_TARGETING = ['mimes', 'minduration', 'maxduration', 'protocols', 'startdelay', 'linearity', 'boxingallowed', 'playbackmethod', 'delivery', 'pos', 'api', 'ext']; -const VERSION = '1.0'; +const VERSION = '1.1'; /** * Adapter for requesting bids from AdKernel white-label display platform @@ -76,8 +76,8 @@ export const spec = { }; if ('banner' in imp) { prBid.mediaType = BANNER; - prBid.width = imp.banner.w; - prBid.height = imp.banner.h; + prBid.width = rtbBid.w; + prBid.height = rtbBid.h; prBid.ad = formatAdMarkup(rtbBid); } if ('video' in imp) { @@ -96,12 +96,7 @@ export const spec = { return serverResponses.filter(rsp => rsp.body && rsp.body.ext && rsp.body.ext.adk_usersync) .map(rsp => rsp.body.ext.adk_usersync) .reduce((a, b) => a.concat(b), []) - .map(sync_url => { - return { - type: 'iframe', - url: sync_url - } - }); + .map(sync_url => ({type: 'iframe', url: sync_url})); } }; @@ -111,21 +106,24 @@ registerBidder(spec); * Builds parameters object for single impression */ function buildImp(bid) { - const size = getAdUnitSize(bid); + const sizes = bid.sizes; const imp = { 'id': bid.bidId, 'tagid': bid.placementCode }; if (bid.mediaType === 'video') { - imp.video = {w: size[0], h: size[1]}; + imp.video = {w: sizes[0], h: sizes[1]}; if (bid.params.video) { Object.keys(bid.params.video) .filter(param => includes(VIDEO_TARGETING, param)) .forEach(param => imp.video[param] = bid.params.video[param]); } } else { - imp.banner = {w: size[0], h: size[1]}; + imp.banner = { + format: sizes.map(s => ({'w': s[0], 'h': s[1]})), + topframe: 0 + }; } if (utils.getTopWindowLocation().protocol === 'https:') { imp.secure = 1; @@ -133,34 +131,36 @@ function buildImp(bid) { return imp; } -/** - * Return ad unit single size - * @param bid adunit size definition - * @return {*} - */ -function getAdUnitSize(bid) { - if (bid.mediaType === 'video') { - return bid.sizes; - } - return bid.sizes[0]; -} - /** * Builds complete rtb request * @param imps collection of impressions * @param auctionId */ function buildRtbRequest(imps, auctionId) { - return { + let req = { 'id': auctionId, 'imp': imps, 'site': createSite(), 'at': 1, 'device': { 'ip': 'caller', - 'ua': 'caller' + 'ua': 'caller', + 'js': 1, + 'language': getLanguage() + }, + 'ext': { + 'adk_usersync': 1 } }; + if (utils.getDNT()) { + req.device.dnt = 1; + } + return req; +} + +function getLanguage() { + const language = navigator.language ? 'language' : 'userLanguage'; + return navigator[language].split('-')[0]; } /** diff --git a/modules/adkernelBidAdapter.md b/modules/adkernelBidAdapter.md index bc30e6cc8e5..902be481473 100644 --- a/modules/adkernelBidAdapter.md +++ b/modules/adkernelBidAdapter.md @@ -10,7 +10,6 @@ Maintainer: denis@adkernel.com Connects to AdKernel whitelabel platform. Banner and video formats are supported. -The AdKernel adapter doesn't support multiple sizes per ad-unit and will use the first one if multiple sizes are defined. # Test Parameters diff --git a/modules/aolBidAdapter.js b/modules/aolBidAdapter.js index 9b4aa26e1a1..35acb95cdee 100644 --- a/modules/aolBidAdapter.js +++ b/modules/aolBidAdapter.js @@ -345,7 +345,7 @@ export const spec = { let bidResponse = bidResponses[0]; if (config.getConfig('aol.userSyncOn') === constants.EVENTS.BID_RESPONSE) { - if (!$$PREBID_GLOBAL$$.aolGlobals.pixelsDropped && bidResponse.ext && bidResponse.ext.pixels) { + if (!$$PREBID_GLOBAL$$.aolGlobals.pixelsDropped && bidResponse && bidResponse.ext && bidResponse.ext.pixels) { $$PREBID_GLOBAL$$.aolGlobals.pixelsDropped = true; return parsePixelItems(bidResponse.ext.pixels); diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index 6e5d4820ee1..96163518a3a 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -103,7 +103,7 @@ export const spec = { const rtbBid = getRtbBid(serverBid); if (rtbBid) { if (rtbBid.cpm !== 0 && includes(this.supportedMediaTypes, rtbBid.ad_type)) { - const bid = newBid(serverBid, rtbBid); + const bid = newBid(serverBid, rtbBid, bidderRequest); bid.mediaType = parseMediaType(rtbBid); bids.push(bid); } @@ -123,11 +123,11 @@ export const spec = { } } -function newRenderer(adUnitCode, rtbBid) { +function newRenderer(adUnitCode, rtbBid, rendererOptions = {}) { const renderer = Renderer.install({ id: rtbBid.renderer_id, url: rtbBid.renderer_url, - config: { adText: `AppNexus Outstream Video Ad via Prebid.js` }, + config: rendererOptions, loaded: false, }); @@ -178,9 +178,10 @@ function getKeywords(keywords) { * Unpack the Server's Bid into a Prebid-compatible one. * @param serverBid * @param rtbBid + * @param bidderRequest * @return Bid */ -function newBid(serverBid, rtbBid) { +function newBid(serverBid, rtbBid, bidderRequest) { const bid = { requestId: serverBid.uuid, cpm: rtbBid.cpm, @@ -200,9 +201,14 @@ function newBid(serverBid, rtbBid) { }); // This supports Outstream Video if (rtbBid.renderer_url) { + const rendererOptions = utils.deepAccess( + bidderRequest.bids[0], + 'renderer.options' + ); + Object.assign(bid, { adResponse: serverBid, - renderer: newRenderer(bid.adUnitCode, rtbBid) + renderer: newRenderer(bid.adUnitCode, rtbBid, rendererOptions) }); bid.adResponse.ad = bid.adResponse.ads[0]; bid.adResponse.ad.video = bid.adResponse.ad.rtb.video; diff --git a/modules/audienceNetworkBidAdapter.js b/modules/audienceNetworkBidAdapter.js index 2b0e4cd8190..3153a64a48d 100644 --- a/modules/audienceNetworkBidAdapter.js +++ b/modules/audienceNetworkBidAdapter.js @@ -143,7 +143,7 @@ const buildRequests = bids => { }; const video = findIndex(adformats, isVideo); if (video !== -1) { - [search.playerwidth, search.playerheight] = sizes[video].split('x').map(Number) + [search.playerwidth, search.playerheight] = expandSize(sizes[video]); } const data = formatQS(search); @@ -162,51 +162,50 @@ const buildRequests = bids => { const interpretResponse = ({ body }, { adformats, requestIds, sizes }) => { const ttl = Number(config.getConfig().bidderTimeout); - return body.errors && body.errors.length - ? [] - : Object.keys(body.bids) - // extract Array of bid responses - .map(placementId => body.bids[placementId]) - // flatten - .reduce((a, b) => a.concat(b), []) - // transform to bidResponse - .map((bid, i) => { - const { - bid_id: fb_bidid, - placement_id: creativeId, - bid_price_cents: cpm - } = bid; - - const format = adformats[i]; - const [width, height] = expandSize(flattenSize(sizes[i])); - const ad = createAdHtml(creativeId, format, fb_bidid); - const requestId = requestIds[i]; - - const bidResponse = { - // Prebid attributes - requestId, - cpm: cpm / 100, - width, - height, - ad, - ttl, - creativeId, - netRevenue, - currency, - // Audience Network attributes - hb_bidder, - fb_bidid, - fb_format: format, - fb_placementid: creativeId - }; - // Video attributes - if (isVideo(format)) { - const pageurl = getTopWindowUrlEncoded(); - bidResponse.mediaType = 'video'; - bidResponse.vastUrl = `https://an.facebook.com/v1/instream/vast.xml?placementid=${creativeId}&pageurl=${pageurl}&playerwidth=${width}&playerheight=${height}&bidid=${fb_bidid}`; - } - return bidResponse; - }); + const { bids = {} } = body; + return Object.keys(bids) + // extract Array of bid responses + .map(placementId => bids[placementId]) + // flatten + .reduce((a, b) => a.concat(b), []) + // transform to bidResponse + .map((bid, i) => { + const { + bid_id: fb_bidid, + placement_id: creativeId, + bid_price_cents: cpm + } = bid; + + const format = adformats[i]; + const [width, height] = expandSize(flattenSize(sizes[i])); + const ad = createAdHtml(creativeId, format, fb_bidid); + const requestId = requestIds[i]; + + const bidResponse = { + // Prebid attributes + requestId, + cpm: cpm / 100, + width, + height, + ad, + ttl, + creativeId, + netRevenue, + currency, + // Audience Network attributes + hb_bidder, + fb_bidid, + fb_format: format, + fb_placementid: creativeId + }; + // Video attributes + if (isVideo(format)) { + const pageurl = getTopWindowUrlEncoded(); + bidResponse.mediaType = 'video'; + bidResponse.vastUrl = `https://an.facebook.com/v1/instream/vast.xml?placementid=${creativeId}&pageurl=${pageurl}&playerwidth=${width}&playerheight=${height}&bidid=${fb_bidid}`; + } + return bidResponse; + }); }; export const spec = { diff --git a/modules/c1xBidAdapter.js b/modules/c1xBidAdapter.js new file mode 100644 index 00000000000..af1de373985 --- /dev/null +++ b/modules/c1xBidAdapter.js @@ -0,0 +1,167 @@ +import { registerBidder } from 'src/adapters/bidderFactory'; +import * as utils from 'src/utils'; +import { userSync } from 'src/userSync'; + +const BIDDER_CODE = 'c1x'; +const URL = 'https://ht.c1exchange.com/ht'; +const PIXEL_ENDPOINT = '//px.c1exchange.com/pubpixel/'; +const LOG_MSG = { + invalidBid: 'C1X: [ERROR] bidder returns an invalid bid', + noSite: 'C1X: [ERROR] no site id supplied', + noBid: 'C1X: [INFO] creating a NO bid for Adunit: ', + bidWin: 'C1X: [INFO] creating a bid for Adunit: ' +}; + +/** + * Adapter for requesting bids from C1X header tag server. + * v3.0 (c) C1X Inc., 2017 + */ + +export const c1xAdapter = { + code: BIDDER_CODE, + + // check the bids sent to c1x bidder + isBidRequestValid: function(bid) { + const siteId = bid.params.siteId || ''; + if (!siteId) { + utils.logError(LOG_MSG.noSite); + } + return !!(bid.adUnitCode && siteId); + }, + + buildRequests: function(bidRequests) { + let payload = {}; + let tagObj = {}; + const adunits = bidRequests.length; + const rnd = new Date().getTime(); + const c1xTags = bidRequests.map(bidToTag); + const bidIdTags = bidRequests.map(bidToShortTag); // include only adUnitCode and bidId from request obj + + // flattened tags in a tag object + tagObj = c1xTags.reduce((current, next) => Object.assign(current, next)); + const pixelId = tagObj.pixelId; + const useSSL = document.location.protocol; + + payload = { + adunits: adunits.toString(), + rnd: rnd.toString(), + response: 'json', + compress: 'gzip' + } + Object.assign(payload, tagObj); + + let payloadString = stringifyPayload(payload); + + if (pixelId) { + const pixelUrl = (useSSL ? 'https:' : 'http:') + PIXEL_ENDPOINT + pixelId; + userSync.registerSync('image', BIDDER_CODE, pixelUrl); + } + + // ServerRequest object + return { + method: 'GET', + url: URL, + data: payloadString, + bids: bidIdTags + }; + }, + + interpretResponse: function(serverResponse, requests) { + serverResponse = serverResponse.body; + requests = requests.bids || []; + const currency = 'USD'; + const bidResponses = []; + let netRevenue = false; + + if (!serverResponse || serverResponse.error) { + let errorMessage = serverResponse.error; + utils.logError(LOG_MSG.invalidBid + errorMessage); + return bidResponses; + } else { + serverResponse.forEach(bid => { + if (bid.bid) { + if (bid.bidType === 'NET_BID') { + netRevenue = !netRevenue; + } + const curBid = { + width: bid.width, + height: bid.height, + cpm: bid.cpm, + ad: bid.ad, + creativeId: bid.crid, + currency: currency, + ttl: 300, + netRevenue: netRevenue + }; + + for (let i = 0; i < requests.length; i++) { + if (bid.adId === requests[i].adUnitCode) { + curBid.requestId = requests[i].bidId; + } + } + utils.logInfo(LOG_MSG.bidWin + bid.adId + ' size: ' + curBid.width + 'x' + curBid.height); + bidResponses.push(curBid); + } else { + // no bid + utils.logInfo(LOG_MSG.noBid + bid.adId); + } + }); + } + + return bidResponses; + } +} + +function bidToTag(bid, index) { + const tag = {}; + const adIndex = 'a' + (index + 1).toString(); // ad unit id for c1x + const sizeKey = adIndex + 's'; + const priceKey = adIndex + 'p'; + // TODO: Multiple Floor Prices + + const sizesArr = bid.sizes; + const floorPriceMap = bid.params.floorPriceMap || ''; + tag['site'] = bid.params.siteId || ''; + + // prevent pixelId becoming undefined when publishers don't fill this param in ad units they have on the same page + if (bid.params.pixelId) { + tag['pixelId'] = bid.params.pixelId + } + + tag[adIndex] = bid.adUnitCode; + tag[sizeKey] = sizesArr.reduce((prev, current) => prev + (prev === '' ? '' : ',') + current.join('x'), ''); + + const newSizeArr = tag[sizeKey].split(','); + if (floorPriceMap) { + newSizeArr.forEach(size => { + if (size in floorPriceMap) { + tag[priceKey] = floorPriceMap[size].toString(); + } // we only accept one cpm price in floorPriceMap + }); + } + if (bid.params.pageurl) { + tag['pageurl'] = bid.params.pageurl; + } + + return tag; +} + +function bidToShortTag(bid) { + const tag = {}; + tag.adUnitCode = bid.adUnitCode; + tag.bidId = bid.bidId; + + return tag; +} + +function stringifyPayload(payload) { + let payloadString = ''; + payloadString = JSON.stringify(payload).replace(/":"|","|{"|"}/g, (foundChar) => { + if (foundChar == '":"') return '='; + else if (foundChar == '","') return '&'; + else return ''; + }); + return payloadString; +} + +registerBidder(c1xAdapter); diff --git a/modules/c1xBidAdapter.md b/modules/c1xBidAdapter.md new file mode 100644 index 00000000000..83a4ff1ea81 --- /dev/null +++ b/modules/c1xBidAdapter.md @@ -0,0 +1,32 @@ +# Overview + +Module Name: C1X Bidder Adapter +Module Type: Bidder Adapter +Maintainer: cathy@c1exchange.com + +# Description + +Module that connects to C1X's demand sources + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + sizes: [[300, 600], [300, 250]], + bids: [ + { + bidder: 'c1x', + params: { + siteId: '9999', + pixelId: '12345', + floorPriceMap: { + '300x250': 0.20, + '300x600': 0.30 + }, //optional + } + } + ] + }, + ]; +``` \ No newline at end of file diff --git a/modules/danmarketplaceBidAdapter.js b/modules/danmarketplaceBidAdapter.js new file mode 100644 index 00000000000..24b3682042e --- /dev/null +++ b/modules/danmarketplaceBidAdapter.js @@ -0,0 +1,143 @@ +import * as utils from 'src/utils'; +import {registerBidder} from 'src/adapters/bidderFactory'; +const BIDDER_CODE = 'danmarketplace'; +const ENDPOINT_URL = '//ads.danmarketplace.com/hb'; +const TIME_TO_LIVE = 360; +const ADAPTER_SYNC_URL = '//ads.danmarketplace.com/push_sync'; +const LOG_ERROR_MESS = { + noAuid: 'Bid from response has no auid parameter - ', + noAdm: 'Bid from response has no adm parameter - ', + noBid: 'Array of bid objects is empty', + noPlacementCode: 'Can\'t find in requested bids the bid with auid - ', + emptyUids: 'Uids should be not empty', + emptySeatbid: 'Seatbid array from response has empty item', + emptyResponse: 'Response is empty', + hasEmptySeatbidArray: 'Response has empty seatbid array', + hasNoArrayOfBids: 'Seatbid from response has no array of bid objects - ' +}; + +/** + * Dentsu Aegis Network Marketplace Bid Adapter. + * Contact: niels@baarsma.net + * + */ +export const spec = { + code: BIDDER_CODE, + + aliases: ['DANMarketplace', 'DAN_Marketplace'], + + isBidRequestValid: function(bid) { + return !!bid.params.uid; + }, + + buildRequests: function(validBidRequests) { + const auids = []; + const bidsMap = {}; + const bids = validBidRequests || []; + let priceType = 'net'; + let reqId; + + bids.forEach(bid => { + if (bid.params.priceType === 'gross') { + priceType = 'gross'; + } + if (!bidsMap[bid.params.uid]) { + bidsMap[bid.params.uid] = [bid]; + auids.push(bid.params.uid); + } else { + bidsMap[bid.params.uid].push(bid); + } + reqId = bid.bidderRequestId; + }); + + const payload = { + u: utils.getTopWindowUrl(), + pt: priceType, + auids: auids.join(','), + r: reqId, + }; + + return { + method: 'GET', + url: ENDPOINT_URL, + data: payload, + bidsMap: bidsMap, + }; + }, + + interpretResponse: function(serverResponse, bidRequest) { + serverResponse = serverResponse && serverResponse.body + const bidResponses = []; + const bidsMap = bidRequest.bidsMap; + const priceType = bidRequest.data.pt; + + let errorMessage; + + if (!serverResponse) errorMessage = LOG_ERROR_MESS.emptyResponse; + else if (serverResponse.seatbid && !serverResponse.seatbid.length) { + errorMessage = LOG_ERROR_MESS.hasEmptySeatbidArray; + } + + if (!errorMessage && serverResponse.seatbid) { + serverResponse.seatbid.forEach(respItem => { + _addBidResponse(_getBidFromResponse(respItem), bidsMap, priceType, bidResponses); + }); + } + if (errorMessage) utils.logError(errorMessage); + return bidResponses; + }, + + getUserSyncs: function(syncOptions) { + if (syncOptions.pixelEnabled) { + return [{ + type: 'image', + url: ADAPTER_SYNC_URL + }]; + } + } +} + +function _getBidFromResponse(respItem) { + if (!respItem) { + utils.logError(LOG_ERROR_MESS.emptySeatbid); + } else if (!respItem.bid) { + utils.logError(LOG_ERROR_MESS.hasNoArrayOfBids + JSON.stringify(respItem)); + } else if (!respItem.bid[0]) { + utils.logError(LOG_ERROR_MESS.noBid); + } + return respItem && respItem.bid && respItem.bid[0]; +} + +function _addBidResponse(serverBid, bidsMap, priceType, bidResponses) { + if (!serverBid) return; + let errorMessage; + if (!serverBid.auid) errorMessage = LOG_ERROR_MESS.noAuid + JSON.stringify(serverBid); + if (!serverBid.adm) errorMessage = LOG_ERROR_MESS.noAdm + JSON.stringify(serverBid); + else { + const awaitingBids = bidsMap[serverBid.auid]; + if (awaitingBids) { + awaitingBids.forEach(bid => { + const bidResponse = { + requestId: bid.bidId, // bid.bidderRequestId, + cpm: serverBid.price, + width: serverBid.w, + height: serverBid.h, + creativeId: serverBid.auid, // bid.bidId, + currency: 'USD', + netRevenue: priceType !== 'gross', + ttl: TIME_TO_LIVE, + ad: serverBid.adm, + dealId: serverBid.dealid + }; + bidResponses.push(bidResponse); + }); + } else { + errorMessage = LOG_ERROR_MESS.noPlacementCode + serverBid.auid; + } + } + if (errorMessage) { + utils.logError(errorMessage); + } +} + +registerBidder(spec); diff --git a/modules/danmarketplaceBidAdapter.md b/modules/danmarketplaceBidAdapter.md new file mode 100755 index 00000000000..263385949bd --- /dev/null +++ b/modules/danmarketplaceBidAdapter.md @@ -0,0 +1,40 @@ +# Overview + +Module Name: Dentsu Aegis Network Marketplace Bidder Adapter +Module Type: Bidder Adapter +Maintainer: niels@baarsma.net + +# Description + +Module that connects to DAN Marketplace demand source to fetch bids. + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + sizes: [[300, 250]], + bids: [ + { + bidder: "danmarketplace", + params: { + uid: '4', + priceType: 'gross' // by default is 'net' + } + } + ] + },{ + code: 'test-div', + sizes: [[728, 90]], + bids: [ + { + bidder: "danmarketplace", + params: { + uid: 5, + priceType: 'gross' + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/modules/iasBidAdapter.js b/modules/iasBidAdapter.js new file mode 100644 index 00000000000..61ba909527a --- /dev/null +++ b/modules/iasBidAdapter.js @@ -0,0 +1,117 @@ +import * as utils from 'src/utils'; +import { registerBidder } from 'src/adapters/bidderFactory'; + +const BIDDER_CODE = 'ias'; + +function isBidRequestValid(bid) { + const { pubId, adUnitPath } = bid.params; + return !!(pubId && adUnitPath); +} + +/** + * Converts GPT-style size array into a string + * @param {Array} sizes: list of GPT-style sizes, e.g. [[300, 250], [300, 300]] + * @return {String} a string containing sizes, e.g. '[300.250,300.300]' + */ +function stringifySlotSizes(sizes) { + let result = ''; + if (utils.isArray(sizes)) { + result = sizes.reduce((acc, size) => { + acc.push(size.join('.')); + return acc; + }, []); + result = '[' + result.join(',') + ']'; + } + return result; +} + +function stringifySlot(bidRequest) { + const id = bidRequest.adUnitCode; + const ss = stringifySlotSizes(bidRequest.sizes); + const p = bidRequest.params.adUnitPath; + const slot = { id, ss, p }; + const keyValues = utils.getKeys(slot).map(function(key) { + return [key, slot[key]].join(':'); + }); + return '{' + keyValues.join(',') + '}'; +} + +function stringifyWindowSize() { + return [window.innerWidth || -1, window.innerHeight || -1].join('.'); +} + +function stringifyScreenSize() { + return [(window.screen && window.screen.width) || -1, (window.screen && window.screen.height) || -1].join('.'); +} + +function buildRequests(bidRequests) { + const IAS_HOST = '//pixel.adsafeprotected.com/services/pub'; + const anId = bidRequests[0].params.pubId; + + let queries = []; + queries.push(['anId', anId]); + queries = queries.concat(bidRequests.reduce(function(acc, request) { + acc.push(['slot', stringifySlot(request)]); + return acc; + }, [])); + + queries.push(['wr', stringifyWindowSize()]); + queries.push(['sr', stringifyScreenSize()]); + + const queryString = encodeURI(queries.map(qs => qs.join('=')).join('&')); + + return { + method: 'GET', + url: IAS_HOST, + data: queryString, + bidRequest: bidRequests[0] + } +} + +function getPageLevelKeywords(response) { + let result = {}; + shallowMerge(result, response.brandSafety); + result.fr = response.fr; + return result; +} + +function shallowMerge(dest, src) { + utils.getKeys(src).reduce((dest, srcKey) => { + dest[srcKey] = src[srcKey]; + return dest; + }, dest); +} + +function interpretResponse(serverResponse, request) { + const iasResponse = serverResponse.body; + const bidResponses = []; + + // Keys in common bid response are not used; + // Necessary to get around with prebid's common bid response check + const commonBidResponse = { + requestId: request.bidRequest.bidId, + cpm: 0.01, + width: 100, + height: 200, + creativeId: 434, + dealId: 42, + currency: 'usd', + netRevenue: true, + ttl: 360 + }; + + shallowMerge(commonBidResponse, getPageLevelKeywords(iasResponse)); + commonBidResponse.slots = iasResponse.slots; + bidResponses.push(commonBidResponse); + return bidResponses; +} + +export const spec = { + code: BIDDER_CODE, + aliases: [], + isBidRequestValid: isBidRequestValid, + buildRequests: buildRequests, + interpretResponse: interpretResponse +}; + +registerBidder(spec); diff --git a/modules/iasBidAdapter.md b/modules/iasBidAdapter.md new file mode 100644 index 00000000000..3224fbf4a26 --- /dev/null +++ b/modules/iasBidAdapter.md @@ -0,0 +1,30 @@ +# Overview + +``` +Module Name: Integral Ad Science(IAS) Bidder Adapter +Module Type: Bidder Adapter +Maintainer: kat@integralads.com +``` + +# Description + +This module is an integration with prebid.js with an IAS product, pet.js. It is not a bidder per se but works in a similar way: retrieve data that publishers might be interested in setting keyword targeting. + +# Test Parameters +``` + var adUnits = [ + { + code: 'ias-dfp-test-async', + sizes: [[300, 250]], // a display size + bids: [ + { + bidder: "ias", + params: { + pubId: '99', + adUnitPath: '/57514611/news.com' + } + } + ] + } + ]; +``` diff --git a/modules/improvedigitalBidAdapter.js b/modules/improvedigitalBidAdapter.js index 82c045b9db6..2dcdcb2a808 100644 --- a/modules/improvedigitalBidAdapter.js +++ b/modules/improvedigitalBidAdapter.js @@ -1,11 +1,12 @@ import * as utils from 'src/utils'; import { registerBidder } from 'src/adapters/bidderFactory'; +import { config } from 'src/config'; import { userSync } from 'src/userSync'; const BIDDER_CODE = 'improvedigital'; export const spec = { - version: '4.0.0', + version: '4.1.0', code: BIDDER_CODE, aliases: ['id'], @@ -113,6 +114,7 @@ function getNormalizedBidRequest(bid) { let localSize = utils.getBidIdParameter('size', bid.params) || null; let bidId = utils.getBidIdParameter('bidId', bid); let transactionId = utils.getBidIdParameter('transactionId', bid); + const currency = config.getConfig('currency.adServerCurrency'); let normalizedBidRequest = {}; if (placementId) { @@ -143,6 +145,9 @@ function getNormalizedBidRequest(bid) { if (transactionId) { normalizedBidRequest.transactionId = transactionId; } + if (currency) { + normalizedBidRequest.currency = currency; + } return normalizedBidRequest; } registerBidder(spec); @@ -160,7 +165,7 @@ function ImproveDigitalAdServerJSClient(endPoint) { AD_SERVER_BASE_URL: 'ad.360yield.com', END_POINT: endPoint || 'hb', AD_SERVER_URL_PARAM: 'jsonp=', - CLIENT_VERSION: 'JS-4.2.0', + CLIENT_VERSION: 'JS-4.3.3', MAX_URL_LENGTH: 2083, ERROR_CODES: { BAD_HTTP_REQUEST_TYPE_PARAM: 1, @@ -337,6 +342,9 @@ function ImproveDigitalAdServerJSClient(endPoint) { if (placementObject.adUnitId) { outputObject.adUnitId = placementObject.adUnitId; } + if (placementObject.currency) { + impressionObject.currency = placementObject.currency.toUpperCase(); + } if (placementObject.placementId) { impressionObject.pid = placementObject.placementId; } diff --git a/modules/improvedigitalBidAdapter.md b/modules/improvedigitalBidAdapter.md index 3d91d4f82f2..d70b624171f 100644 --- a/modules/improvedigitalBidAdapter.md +++ b/modules/improvedigitalBidAdapter.md @@ -12,6 +12,7 @@ Module that connects to Improve Digital's demand sources ``` var adUnits = [{ code: 'div-gpt-ad-1499748733608-0', + sizes: [[600, 290]], bids: [ { bidder: 'improvedigital', @@ -22,6 +23,7 @@ Module that connects to Improve Digital's demand sources ] }, { code: 'div-gpt-ad-1499748833901-0', + sizes: [[250, 250]], bids: [{ bidder: 'improvedigital', params: { @@ -33,6 +35,7 @@ Module that connects to Improve Digital's demand sources }] }, { code: 'div-gpt-ad-1499748913322-0', + sizes: [[300, 300]], bids: [{ bidder: 'improvedigital', params: { diff --git a/modules/nanointeractiveBidAdapter.js b/modules/nanointeractiveBidAdapter.js index c4cce6646d3..225859a4360 100644 --- a/modules/nanointeractiveBidAdapter.js +++ b/modules/nanointeractiveBidAdapter.js @@ -3,28 +3,21 @@ import { registerBidder } from '../src/adapters/bidderFactory'; import { BANNER } from '../src/mediaTypes'; export const BIDDER_CODE = 'nanointeractive'; -export const ENGINE_BASE_URL = 'http://tmp.audiencemanager.de/hb'; +export const ENGINE_BASE_URL = 'https://www.audiencemanager.de/hb'; -export const SECURITY = 'sec'; -export const DATA_PARTNER_ID = 'dpid'; export const DATA_PARTNER_PIXEL_ID = 'pid'; -export const ALG = 'alg'; export const NQ = 'nq'; export const NQ_NAME = 'name'; export const CATEGORY = 'category'; -const DEFAULT_ALG = 'ihr'; - export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER], isBidRequestValid(bid) { - const sec = bid.params[SECURITY]; - const dpid = bid.params[DATA_PARTNER_ID]; const pid = bid.params[DATA_PARTNER_PIXEL_ID]; - return !!(sec && dpid && pid); + return !!(pid); }, buildRequests(bidRequests) { let payload = []; @@ -37,7 +30,7 @@ export const spec = { }, interpretResponse(serverResponse) { const bids = []; - serverResponse.forEach(serverBid => { + serverResponse.body.forEach(serverBid => { if (isEngineResponseValid(serverBid)) { bids.push(createSingleBidResponse(serverBid)); } @@ -48,10 +41,7 @@ export const spec = { function createSingleBidRequest(bid) { return { - [SECURITY]: bid.params[SECURITY], - [DATA_PARTNER_ID]: bid.params[DATA_PARTNER_ID], [DATA_PARTNER_PIXEL_ID]: bid.params[DATA_PARTNER_PIXEL_ID], - [ALG]: bid.params[ALG] || DEFAULT_ALG, [NQ]: [createNqParam(bid), createCategoryParam(bid)], sizes: bid.sizes.map(value => value[0] + 'x' + value[1]), bidId: bid.bidId, diff --git a/modules/nanointeractiveBidAdapter.md b/modules/nanointeractiveBidAdapter.md index 0159353750a..0df49999492 100644 --- a/modules/nanointeractiveBidAdapter.md +++ b/modules/nanointeractiveBidAdapter.md @@ -31,8 +31,6 @@ var adUnits = [ bids: [{ bidder: 'nanointeractive', params: { - sec: '04a0cb7fb9ac02840f7f33d68a883780', - dpid: '58bfec94eb0a1916fa380162', pid: '58bfec94eb0a1916fa380163' } }] @@ -44,8 +42,6 @@ var adUnits = [ bids: [{ bidder: 'nanointeractive', params: { - sec: '04a0cb7fb9ac02840f7f33d68a883780', - dpid: '58bfec94eb0a1916fa380162', pid: '58bfec94eb0a1916fa380163', nq: 'user search' } @@ -58,8 +54,6 @@ var adUnits = [ bids: [{ bidder: 'nanointeractive', params: { - sec: '04a0cb7fb9ac02840f7f33d68a883780', - dpid: '58bfec94eb0a1916fa380162', pid: '58bfec94eb0a1916fa380163', name: 'search' } diff --git a/modules/optimaticBidAdapter.js b/modules/optimaticBidAdapter.js index 2408eb8cefe..0c8305e6867 100644 --- a/modules/optimaticBidAdapter.js +++ b/modules/optimaticBidAdapter.js @@ -3,6 +3,9 @@ import { registerBidder } from 'src/adapters/bidderFactory'; export const ENDPOINT = '//mg-bid.optimatic.com/adrequest/'; export const spec = { + + version: '1.0.4', + code: 'optimatic', supportedMediaTypes: ['video'], @@ -33,7 +36,7 @@ export const spec = { } catch (e) { response = null; } - if (!response || !bid || !bid.adm || !bid.price) { + if (!response || !bid || (!bid.adm && !bid.nurl) || !bid.price) { utils.logWarn(`No valid bids from ${spec.code} bidder`); return []; } @@ -43,7 +46,6 @@ export const spec = { bidderCode: spec.code, cpm: bid.price, creativeId: bid.id, - vastXml: bid.adm, width: size.width, height: size.height, mediaType: 'video', @@ -51,6 +53,11 @@ export const spec = { ttl: 300, netRevenue: true }; + if (bid.nurl) { + bidResponse.vastUrl = bid.nurl; + } else if (bid.adm) { + bidResponse.vastXml = bid.adm; + } return bidResponse; } }; diff --git a/modules/peak226BidAdapter.js b/modules/peak226BidAdapter.js new file mode 100644 index 00000000000..4f4ee2f97ff --- /dev/null +++ b/modules/peak226BidAdapter.js @@ -0,0 +1,97 @@ +import { registerBidder } from 'src/adapters/bidderFactory'; +import { BANNER } from 'src/mediaTypes'; +import { getTopWindowUrl, logWarn } from 'src/utils'; + +const BIDDER_CODE = 'peak226'; +const URL = '//a.ad216.com/header_bid'; + +export const spec = { + code: BIDDER_CODE, + + supportedMediaTypes: [BANNER], + + isBidRequestValid: function (bid) { + const { params } = bid; + + return !!params.uid; + }, + + buildRequests: function (validBidRequests) { + const bidsMap = validBidRequests.reduce((res, bid) => { + const { uid } = bid.params; + + res[uid] = res[uid] || []; + res[uid].push(bid); + + return res; + }, {}); + + return { + method: 'GET', + url: + URL + + toQueryString({ + u: getTopWindowUrl(), + auids: Object.keys(bidsMap).join(',') + }), + bidsMap + }; + }, + + interpretResponse: function (serverResponse, { bidsMap }) { + const response = serverResponse.body; + const bidResponses = []; + + if (!response) { + logWarn(`No response from ${spec.code} bidder`); + + return bidResponses; + } + + if (!response.seatbid || !response.seatbid.length) { + logWarn(`No seatbid in response from ${spec.code} bidder`); + + return bidResponses; + } + + response.seatbid.forEach((seatbid, i) => { + if (!seatbid.bid || !seatbid.bid.length) { + logWarn(`No bid in seatbid[${i}] response from ${spec.code} bidder`); + return; + } + seatbid.bid.forEach(responseBid => { + const requestBids = bidsMap[responseBid.auid]; + + requestBids.forEach(requestBid => { + bidResponses.push({ + requestId: requestBid.bidId, + bidderCode: spec.code, + width: responseBid.w, + height: responseBid.h, + mediaType: BANNER, + creativeId: responseBid.auid, + ad: responseBid.adm, + cpm: responseBid.price, + currency: 'USD', + netRevenue: true, + ttl: 360 + }); + }); + }); + }); + + return bidResponses; + } +}; + +function toQueryString(obj) { + return Object.keys(obj).reduce( + (str, key, i) => + typeof obj[key] === 'undefined' || obj[key] === '' + ? str + : `${str}${str ? '&' : '?'}${key}=${encodeURIComponent(obj[key])}`, + '' + ); +} + +registerBidder(spec); diff --git a/modules/peak226BidAdapter.md b/modules/peak226BidAdapter.md new file mode 100644 index 00000000000..bae15d6c99f --- /dev/null +++ b/modules/peak226BidAdapter.md @@ -0,0 +1,31 @@ +# Overview + +``` +Module Name: Peak226 Bidder Adapter +Module Type: Bidder Adapter +Maintainer: support@edge226.com +``` + +# Description + +Module that connects to Peak226's demand sources + +# Test Parameters + +``` + var adUnits = [ + { + code: "test-div", + sizes: [[300, 250]], + mediaType: "banner", + bids: [ + { + bidder: "peak226", + params: { + uid: 76131369 + } + } + ] + } + ]; +``` diff --git a/modules/platformioBidAdapter.js b/modules/platformioBidAdapter.js index fa841dc6026..93907c9bb1a 100644 --- a/modules/platformioBidAdapter.js +++ b/modules/platformioBidAdapter.js @@ -1,10 +1,19 @@ -import {logError, getTopWindowLocation} from 'src/utils'; +import {logError, getTopWindowLocation, getTopWindowReferrer} from 'src/utils'; import { registerBidder } from 'src/adapters/bidderFactory'; +const NATIVE_DEFAULTS = { + TITLE_LEN: 100, + DESCR_LEN: 200, + SPONSORED_BY_LEN: 50, + IMG_MIN: 150, + ICON_MIN: 50, +}; + export const spec = { code: 'platformio', + supportedMediaTypes: ['native'], isBidRequestValid: bid => ( !!(bid && bid.params && bid.params.pubId && bid.params.siteId) @@ -27,6 +36,7 @@ export const spec = { bidResponseAvailable(request, response.body) ), }; + function bidResponseAvailable(bidRequest, bidResponse) { const idToImpMap = {}; const idToBidMap = {}; @@ -44,19 +54,31 @@ function bidResponseAvailable(bidRequest, bidResponse) { if (idToBidMap[id]) { const bid = {}; bid.requestId = id; - bid.creativeId = idToBidMap[id].adid; + bid.adId = id; + bid.creativeId = id; bid.cpm = idToBidMap[id].price; bid.currency = bidResponse.cur; bid.ttl = 360; bid.netRevenue = true; - bid.ad = idToBidMap[id].adm; - bid.ad = bid.ad.replace(/\$(%7B|\{)AUCTION_IMP_ID(%7D|\})/gi, idToBidMap[id].impid); - bid.ad = bid.ad.replace(/\$(%7B|\{)AUCTION_AD_ID(%7D|\})/gi, idToBidMap[id].adid); - bid.ad = bid.ad.replace(/\$(%7B|\{)AUCTION_PRICE(%7D|\})/gi, idToBidMap[id].price); - bid.ad = bid.ad.replace(/\$(%7B|\{)AUCTION_CURRENCY(%7D|\})/gi, bidResponse.cur); - bid.ad = bid.ad.replace(/\$(%7B|\{)AUCTION_BID_ID(%7D|\})/gi, bidResponse.bidid); - bid.width = idToImpMap[id].banner.w; - bid.height = idToImpMap[id].banner.h; + if (idToImpMap[id]['native']) { + bid['native'] = nativeResponse(idToImpMap[id], idToBidMap[id]); + let nurl = idToBidMap[id].nurl; + nurl = nurl.replace(/\$(%7B|\{)AUCTION_IMP_ID(%7D|\})/gi, idToBidMap[id].impid); + nurl = nurl.replace(/\$(%7B|\{)AUCTION_PRICE(%7D|\})/gi, idToBidMap[id].price); + nurl = nurl.replace(/\$(%7B|\{)AUCTION_CURRENCY(%7D|\})/gi, bidResponse.cur); + nurl = nurl.replace(/\$(%7B|\{)AUCTION_BID_ID(%7D|\})/gi, bidResponse.bidid); + bid['native']['impressionTrackers'] = [nurl]; + bid.mediaType = 'native'; + } else { + bid.ad = idToBidMap[id].adm; + bid.ad = bid.ad.replace(/\$(%7B|\{)AUCTION_IMP_ID(%7D|\})/gi, idToBidMap[id].impid); + bid.ad = bid.ad.replace(/\$(%7B|\{)AUCTION_AD_ID(%7D|\})/gi, idToBidMap[id].adid); + bid.ad = bid.ad.replace(/\$(%7B|\{)AUCTION_PRICE(%7D|\})/gi, idToBidMap[id].price); + bid.ad = bid.ad.replace(/\$(%7B|\{)AUCTION_CURRENCY(%7D|\})/gi, bidResponse.cur); + bid.ad = bid.ad.replace(/\$(%7B|\{)AUCTION_BID_ID(%7D|\})/gi, bidResponse.bidid); + bid.width = idToImpMap[id].banner.w; + bid.height = idToImpMap[id].banner.h; + } bids.push(bid); } }); @@ -66,43 +88,95 @@ function impression(slot) { return { id: slot.bidId, banner: banner(slot), + 'native': nativeImpression(slot), bidfloor: slot.params.bidFloor || '0.000001', tagid: slot.params.placementId.toString(), }; } function banner(slot) { - const size = slot.params.size.toUpperCase().split('X'); - const width = parseInt(size[0]); - const height = parseInt(size[1]); - return { - w: width, - h: height, + if (!slot.nativeParams) { + const size = slot.params.size.toUpperCase().split('X'); + const width = parseInt(size[0]); + const height = parseInt(size[1]); + return { + w: width, + h: height, + }; }; } -function site(bidderRequest) { - const pubId = bidderRequest && bidderRequest.length > 0 ? bidderRequest[0].params.pubId : '0'; - const siteId = bidderRequest && bidderRequest.length > 0 ? bidderRequest[0].params.siteId : '0'; - const appParams = bidderRequest[0].params.app; - if (!appParams) { + +function nativeImpression(slot) { + if (slot.nativeParams) { + const assets = []; + addAsset(assets, titleAsset(1, slot.nativeParams.title, NATIVE_DEFAULTS.TITLE_LEN)); + addAsset(assets, dataAsset(2, slot.nativeParams.body, 2, NATIVE_DEFAULTS.DESCR_LEN)); + addAsset(assets, dataAsset(3, slot.nativeParams.sponsoredBy, 1, NATIVE_DEFAULTS.SPONSORED_BY_LEN)); + addAsset(assets, imageAsset(4, slot.nativeParams.icon, 1, NATIVE_DEFAULTS.ICON_MIN, NATIVE_DEFAULTS.ICON_MIN)); + addAsset(assets, imageAsset(5, slot.nativeParams.image, 3, NATIVE_DEFAULTS.IMG_MIN, NATIVE_DEFAULTS.IMG_MIN)); return { - publisher: { - id: pubId.toString(), - domain: getTopWindowLocation().hostname, + request: JSON.stringify({ assets }), + ver: '1.1', + }; + } + return null; +} + +function addAsset(assets, asset) { + if (asset) { + assets.push(asset); + } +} + +function titleAsset(id, params, defaultLen) { + if (params) { + return { + id, + required: params.required ? 1 : 0, + title: { + len: params.len || defaultLen, }, - id: siteId.toString(), - ref: referrer(), - page: getTopWindowLocation().href, - } + }; } return null; } -function referrer() { - try { - return window.top.document.referrer; - } catch (e) { - return document.referrer; + +function imageAsset(id, params, type, defaultMinWidth, defaultMinHeight) { + return params ? { + id, + required: params.required ? 1 : 0, + img: { + type, + wmin: params.wmin || defaultMinWidth, + hmin: params.hmin || defaultMinHeight, + } + } : null; +} + +function dataAsset(id, params, type, defaultLen) { + return params ? { + id, + required: params.required ? 1 : 0, + data: { + type, + len: params.len || defaultLen, + } + } : null; +} + +function site(bidderRequest) { + const pubId = bidderRequest && bidderRequest.length > 0 ? bidderRequest[0].params.pubId : '0'; + const siteId = bidderRequest && bidderRequest.length > 0 ? bidderRequest[0].params.siteId : '0'; + return { + publisher: { + id: pubId.toString(), + domain: getTopWindowLocation().hostname, + }, + id: siteId.toString(), + ref: getTopWindowReferrer(), + page: getTopWindowLocation().href, } } + function device() { return { ua: navigator.userAgent, @@ -122,4 +196,25 @@ function parse(rawResponse) { return null; } +function nativeResponse(imp, bid) { + if (imp['native']) { + const nativeAd = parse(bid.adm); + const keys = {}; + if (nativeAd && nativeAd['native'] && nativeAd['native'].assets) { + nativeAd['native'].assets.forEach(asset => { + keys.title = asset.title ? asset.title.text : keys.title; + keys.body = asset.data && asset.id === 2 ? asset.data.value : keys.body; + keys.sponsoredBy = asset.data && asset.id === 3 ? asset.data.value : keys.sponsoredBy; + keys.icon = asset.img && asset.id === 4 ? asset.img.url : keys.icon; + keys.image = asset.img && asset.id === 5 ? asset.img.url : keys.image; + }); + if (nativeAd['native'].link) { + keys.clickUrl = encodeURIComponent(nativeAd['native'].link.url); + } + return keys; + } + } + return null; +} + registerBidder(spec); diff --git a/modules/platformioBidAdapter.md b/modules/platformioBidAdapter.md index 0d019d1fe96..81ac81380b0 100644 --- a/modules/platformioBidAdapter.md +++ b/modules/platformioBidAdapter.md @@ -1,12 +1,13 @@ # Overview -**Module Name**: Platform.io Bidder Adapter -**Module Type**: Bidder Adapter -**Maintainer**: sk@ultralab.by +**Module Name**: Platform.io Bidder Adapter +**Module Type**: Bidder Adapter +**Maintainer**: siarhei.kasukhin@platform.io # Description -Connects to Platform.io demand source to fetch bids. +Connects to Platform.io demand source to fetch bids. +Banner and Native formats are supported. Please use ```platformio``` as the bidder code. # Test Parameters @@ -17,11 +18,30 @@ Please use ```platformio``` as the bidder code. bids: [{ bidder: 'platformio', params: { - pubId: '28082', // required - siteId: '26047', // required - size: '250X250', // required - placementId: '123', - bidFloor: '0.001' + pubId: '29521', // required + siteId: '26047', // required + size: '250X250', // required + placementId: '123', + bidFloor: '0.001' + } + }] + },{ + code: 'native-ad-div', + sizes: [[1, 1]], + nativeParams: { + title: { required: true, len: 75 }, + image: { required: true }, + body: { len: 200 }, + sponsoredBy: { len: 20 }, + icon: { required: false } + }, + bids: [{ + bidder: 'platformio', + params: { + pubId: '29521', // required + siteId: '26047', // required + placementId: '123', + bidFloor: '0.001' } }] }]; diff --git a/modules/prebidServerBidAdapter.js b/modules/prebidServerBidAdapter.js index 0540d325c79..7ef83240251 100644 --- a/modules/prebidServerBidAdapter.js +++ b/modules/prebidServerBidAdapter.js @@ -166,6 +166,23 @@ const paramTypes = { }, }; +function _getDigiTrustQueryParams() { + function getDigiTrustId() { + let digiTrustUser = window.DigiTrust && (config.getConfig('digiTrustId') || window.DigiTrust.getUser({member: 'T9QSFKPDN9'})); + return (digiTrustUser && digiTrustUser.success && digiTrustUser.identity) || null; + } + let digiTrustId = getDigiTrustId(); + // Verify there is an ID and this user has not opted out + if (!digiTrustId || (digiTrustId.privacy && digiTrustId.privacy.optout)) { + return null; + } + return { + id: digiTrustId.id, + keyv: digiTrustId.keyv, + pref: 0 + }; +} + /** * Bidder adapter for Prebid Server */ @@ -218,6 +235,20 @@ export function PrebidServer() { is_debug: isDebug }; + let digiTrust = _getDigiTrustQueryParams(); + + // grab some global config and pass it along + ['app', 'device'].forEach(setting => { + let value = getConfig(setting); + if (typeof value === 'object') { + requestJson[setting] = value; + } + }); + + if (digiTrust) { + requestJson.digiTrust = digiTrust; + } + // in case config.bidders contains invalid bidders, we only process those we sent requests for. const requestedBidders = requestJson.ad_units.map(adUnit => adUnit.bids.map(bid => bid.bidder).filter(utils.uniques)).reduce(utils.flatten).filter(utils.uniques); function processResponse(response) { @@ -254,7 +285,7 @@ export function PrebidServer() { requestedBidders.forEach(bidder => { let clientAdapter = adaptermanager.getBidAdapter(bidder); if (clientAdapter && clientAdapter.registerSyncs) { - clientAdapter.registerSyncs(); + clientAdapter.registerSyncs([]); } }); diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 1e7762ae90d..ee490e55c15 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -112,9 +112,10 @@ export const spec = { if (bidRequest.mediaType === 'video') { let params = bidRequest.params; let size = parseSizes(bidRequest); + let page_rf = !params.referrer ? utils.getTopWindowUrl() : params.referrer; let data = { - page_url: !params.referrer ? utils.getTopWindowUrl() : params.referrer, + page_url: params.secure ? page_rf.replace(/^http:/i, 'https:') : page_rf, resolution: _getScreenResolution(), account_id: params.accountId, integration: INTEGRATION, @@ -265,6 +266,7 @@ export const spec = { requestId: bidRequest.bidId, currency: 'USD', creativeId: ad.creative_id, + mediaType: ad.creative_type, cpm: ad.cpm || 0, dealId: ad.deal, ttl: 300, // 5 minutes @@ -275,6 +277,7 @@ export const spec = { bid.height = bidRequest.params.video.playerHeight; bid.vastUrl = ad.creative_depot_url; bid.impression_id = ad.impression_id; + bid.videoCacheKey = ad.impression_id; } else { bid.ad = _renderCreative(ad.script, ad.impression_id); [bid.width, bid.height] = sizeMap[ad.size_id].split('x').map(num => Number(num)); diff --git a/modules/sekindoUMBidAdapter.js b/modules/sekindoUMBidAdapter.js index 09b94be967b..e87f3194ff0 100644 --- a/modules/sekindoUMBidAdapter.js +++ b/modules/sekindoUMBidAdapter.js @@ -104,14 +104,6 @@ export const spec = { bidResponses.push(bidResponse); return bidResponses; - }, - getUserSyncs: function(syncOptions) { - if (syncOptions.iframeEnabled) { - return [{ - type: 'iframe', - url: 'ADAPTER_SYNC_URL' - }]; - } } } registerBidder(spec); diff --git a/modules/sekindoUMBidAdapter.md b/modules/sekindoUMBidAdapter.md index 24c2aaec6db..05c0227976d 100755 --- a/modules/sekindoUMBidAdapter.md +++ b/modules/sekindoUMBidAdapter.md @@ -19,7 +19,7 @@ Banner, Outstream and Native formats are supported. bidder: 'sekindoUM', params: { spaceId: 14071 - width:300, //optional + width:300, ///optional weight:250, //optional } }] diff --git a/modules/serverbidBidAdapter.js b/modules/serverbidBidAdapter.js index 84cae1fa1c0..24bb5aa6255 100644 --- a/modules/serverbidBidAdapter.js +++ b/modules/serverbidBidAdapter.js @@ -18,12 +18,18 @@ const CONFIG = { }, 'adsparc': { 'BASE_URI': 'https://e.serverbid.com/api/v2' + }, + 'automatad': { + 'BASE_URI': 'https://e.serverbid.com/api/v2' } }; +let siteId = 0; +let bidder = 'serverbid'; + export const spec = { code: BIDDER_CODE, - aliases: ['connectad', 'onefiftytwo', 'insticator', 'adsparc'], + aliases: ['connectad', 'onefiftytwo', 'insticator', 'adsparc', 'automatad'], /** * Determines whether or not the given bid request is valid. @@ -59,6 +65,10 @@ export const spec = { let ENDPOINT_URL; + // These variables are used in creating the user sync URL. + siteId = validBidRequests[0].params.siteId; + bidder = validBidRequests[0].params.bidder; + const data = Object.assign({ placements: [], time: Date.now(), @@ -137,7 +147,21 @@ export const spec = { }, getUserSyncs: function(syncOptions) { - return []; + if (syncOptions.iframeEnabled) { + if (bidder === 'connectad') { + return [{ + type: 'iframe', + url: '//cdn.connectad.io/connectmyusers.php' + }]; + } else { + return [{ + type: 'iframe', + url: '//s.zkcdn.net/ss/' + siteId + '.html' + }]; + } + } else { + utils.logWarn(bidder + ': Please enable iframe based user syncing.'); + } } }; @@ -182,6 +206,9 @@ sizeMap[429] = '486x60'; sizeMap[374] = '700x500'; sizeMap[934] = '300x1050'; sizeMap[1578] = '320x100'; +sizeMap[331] = '320x250'; +sizeMap[3301] = '320x267'; +sizeMap[2730] = '728x250'; function getSize(sizes) { const result = []; diff --git a/modules/serverbidServerBidAdapter.js b/modules/serverbidServerBidAdapter.js index 4be96a09bd6..1025d29a6c0 100644 --- a/modules/serverbidServerBidAdapter.js +++ b/modules/serverbidServerBidAdapter.js @@ -7,7 +7,7 @@ import { config } from 'src/config'; const TYPE = S2S.SRC; const getConfig = config.getConfig; -const REQUIRED_S2S_CONFIG_KEYS = ['siteId', 'networkId', 'bidders', 'endpoint']; +const REQUIRED_S2S_CONFIG_KEYS = ['siteId', 'networkId', 'bidders']; let _s2sConfig; @@ -100,30 +100,30 @@ ServerBidServerAdapter = function ServerBidServerAdapter() { function _callBids(s2sBidRequest, bidRequests, addBidResponse, done, ajax) { let bidRequest = s2sBidRequest; - // one request per ad unit + const data = { + placements: [], + time: Date.now(), + user: {}, + url: utils.getTopWindowUrl(), + referrer: document.referrer, + enableBotFiltering: true, + includePricingData: true, + parallel: true + }; + const allBids = []; + for (let i = 0; i < bidRequest.ad_units.length; i++) { let adunit = bidRequest.ad_units[i]; let siteId = _s2sConfig.siteId; let networkId = getLocalConfig().networkId; let sizes = adunit.sizes; - const data = { - placements: [], - time: Date.now(), - user: {}, - url: utils.getTopWindowUrl(), - referrer: document.referrer, - enableBotFiltering: true, - includePricingData: true, - parallel: true - }; - - const bids = adunit.bids || []; - + var bids = adunit.bids || []; // one placement for each of the bids for (let i = 0; i < bids.length; i++) { const bid = bids[i]; bid.code = adunit.code; + allBids.push(bid); const placement = Object.assign({}, { divName: bid.bid_id, @@ -138,9 +138,9 @@ ServerBidServerAdapter = function ServerBidServerAdapter() { data.placements.push(placement); } } - if (data.placements.length) { - ajax(BASE_URI, _responseCallback(addBidResponse, bids, done), JSON.stringify(data), { method: 'POST', withCredentials: true, contentType: 'application/json' }); - } + } + if (data.placements.length) { + ajax(BASE_URI, _responseCallback(addBidResponse, allBids, done), JSON.stringify(data), { method: 'POST', withCredentials: true, contentType: 'application/json' }); } } diff --git a/modules/sonobiBidAdapter.js b/modules/sonobiBidAdapter.js new file mode 100644 index 00000000000..fb5d9a7f56f --- /dev/null +++ b/modules/sonobiBidAdapter.js @@ -0,0 +1,146 @@ +import { registerBidder } from 'src/adapters/bidderFactory'; +import { getTopWindowLocation, parseSizesInput } from 'src/utils'; +import * as utils from '../src/utils'; + +const BIDDER_CODE = 'sonobi'; +const STR_ENDPOINT = 'https://apex.go.sonobi.com/trinity.json'; +const PAGEVIEW_ID = utils.generateUUID(); + +export const spec = { + code: BIDDER_CODE, + + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid - The bid params to validate. + * @return {boolean} True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: bid => !!(bid.params && (bid.params.ad_unit || bid.params.placement_id) && (bid.params.sizes || bid.sizes)), + + /** + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} validBidRequests - an array of bids + * @return {object} ServerRequest - Info describing the request to the server. + */ + buildRequests: (validBidRequests) => { + const bids = validBidRequests.map(bid => { + let slotIdentifier = _validateSlot(bid) + if (/^[\/]?[\d]+[[\/].+[\/]?]?$/.test(slotIdentifier)) { + slotIdentifier = slotIdentifier.charAt(0) === '/' ? slotIdentifier : '/' + slotIdentifier + return { + [`${slotIdentifier}|${bid.bidId}`]: `${_validateSize(bid)}${_validateFloor(bid)}` + } + } else if (/^[0-9a-fA-F]{20}$/.test(slotIdentifier) && slotIdentifier.length === 20) { + return { + [bid.bidId]: `${slotIdentifier}|${_validateSize(bid)}${_validateFloor(bid)}` + } + } else { + utils.logError(`The ad unit code or Sonobi Placement id for slot ${bid.bidId} is invalid`); + } + }); + + let data = {} + bids.forEach((bid) => { Object.assign(data, bid); }); + + const payload = { + 'key_maker': JSON.stringify(data), + 'ref': getTopWindowLocation().host, + 's': utils.generateUUID(), + 'pv': PAGEVIEW_ID, + }; + + if (validBidRequests[0].params.hfa) { + payload.hfa = validBidRequests[0].params.hfa; + } + + return { + method: 'GET', + url: STR_ENDPOINT, + withCredentials: true, + data: payload + }; + }, + /** + * 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: (serverResponse) => { + const bidResponse = serverResponse.body; + const bidsReturned = []; + + if (Object.keys(bidResponse.slots).length === 0) { + return bidsReturned; + } + + Object.keys(bidResponse.slots).forEach(slot => { + const bid = bidResponse.slots[slot]; + + if (bid.sbi_aid && bid.sbi_mouse && bid.sbi_size) { + const bids = { + requestId: slot.split('|').slice(-1)[0], + cpm: Number(bid.sbi_mouse), + width: Number(bid.sbi_size.split('x')[0]) || 1, + height: Number(bid.sbi_size.split('x')[1]) || 1, + ad: _creative(bidResponse.sbi_dc, bid.sbi_aid), + ttl: 500, + creativeId: bid.sbi_aid, + netRevenue: true, + currency: 'USD', + }; + + if (bid.sbi_dozer) { + bids.dealId = bid.sbi_dozer; + } + + bidsReturned.push(bids); + } + }); + return bidsReturned; + }, + /** + * Register User Sync. + */ + getUserSyncs: (syncOptions, serverResponses) => { + const syncs = []; + if (syncOptions.pixelEnabled && serverResponses[0].body.sbi_px) { + serverResponses[0].body.sbi_px.forEach(pixel => { + syncs.push({ + type: pixel.type, + url: pixel.url + }); + }); + } + return syncs; + } +} + +function _validateSize (bid) { + if (bid.params.sizes) { + return parseSizesInput(bid.params.sizes).join(','); + } + return parseSizesInput(bid.sizes).join(','); +} + +function _validateSlot (bid) { + if (bid.params.ad_unit) { + return bid.params.ad_unit; + } + return bid.params.placement_id; +} + +function _validateFloor (bid) { + if (bid.params.floor) { + return `|f=${bid.params.floor}`; + } + return ''; +} + +function _creative (sbi_dc, sbi_aid) { + const src = 'https://' + sbi_dc + 'apex.go.sonobi.com/sbi.js?aid=' + sbi_aid + '&as=null'; + return ''; +} + +registerBidder(spec); diff --git a/modules/sonobiBidAdapter.md b/modules/sonobiBidAdapter.md new file mode 100644 index 00000000000..91e4a0e2b2e --- /dev/null +++ b/modules/sonobiBidAdapter.md @@ -0,0 +1,32 @@ +# Overview + +``` +Module Name: Sonobi Bidder Adapter +Module Type: Bidder Adapter +Maintainer: apex.prebid@sonobi.com +``` + +# Description + +Module that connects to Sonobi's demand sources. + +# Test Parameters +``` + var adUnits = [ + { + code: 'adUnit_af', + sizes: [[300, 250], [300, 600]], // a display size + bids: [ + { + bidder: 'sonobi', + params: { + ad_unit: '/7780971/sparks_prebid_MR', + placement_id: '1a2b3c4d5e6f1a2b3c4d', // ad_unit and placement_id are mutually exclusive + sizes: [[300, 250], [300, 600]], + floor: 1 // optional + } + } + ] + } + ]; +``` diff --git a/modules/sovrnBidAdapter.js b/modules/sovrnBidAdapter.js index bf2f7f7b777..13f04395afa 100644 --- a/modules/sovrnBidAdapter.js +++ b/modules/sovrnBidAdapter.js @@ -66,7 +66,7 @@ export const spec = { width: parseInt(sovrnBid.w), height: parseInt(sovrnBid.h), creativeId: sovrnBid.id, - dealId: sovrnBid.dealId || null, + dealId: sovrnBid.dealid || null, currency: 'USD', netRevenue: true, mediaType: BANNER, diff --git a/modules/ucfunnelBidAdapter.js b/modules/ucfunnelBidAdapter.js new file mode 100644 index 00000000000..7f652e38c3d --- /dev/null +++ b/modules/ucfunnelBidAdapter.js @@ -0,0 +1,95 @@ +import * as utils from 'src/utils'; +import {registerBidder} from 'src/adapters/bidderFactory'; +import { BANNER } from 'src/mediaTypes'; + +const VER = 'ADGENT_PREBID-2018011501'; +const BID_REQUEST_BASE_URL = '//hb.aralego.com/header'; +const UCFUNNEL_BIDDER_CODE = 'ucfunnel'; + +export const spec = { + code: UCFUNNEL_BIDDER_CODE, + supportedMediaTypes: [BANNER], + /** + * Check if the bid is a valid zone ID in either number or string form + * @param {object} bid the ucfunnel bid to validate + * @return boolean for whether or not a bid is valid + */ + isBidRequestValid: function(bid) { + return !!(bid && bid.params && bid.params.adid && typeof bid.params.adid === 'string'); + }, + + /** + * Format the bid request object for our endpoint + * @param {BidRequest[]} bidRequests Array of ucfunnel bidders + * @return object of parameters for Prebid AJAX request + */ + buildRequests: function(validBidRequests) { + var bidRequests = []; + for (var i = 0; i < validBidRequests.length; i++) { + var bid = validBidRequests[i]; + + var ucfunnelUrlParams = buildUrlParams(bid); + + bidRequests.push({ + method: 'GET', + url: BID_REQUEST_BASE_URL, + bidRequest: bid, + data: ucfunnelUrlParams + }); + } + return bidRequests; + }, + + /** + * Format ucfunnel responses as Prebid bid responses + * @param {ucfunnelResponseObj} ucfunnelResponse A successful response from ucfunnel. + * @return {Bid[]} An array of formatted bids. + */ + interpretResponse: function (ucfunnelResponseObj, request) { + var bidResponses = []; + var bidRequest = request.bidRequest; + var responseBody = ucfunnelResponseObj ? ucfunnelResponseObj.body : {}; + + bidResponses.push({ + requestId: bidRequest.bidId, + cpm: responseBody.cpm || 0, + width: responseBody.width, + height: responseBody.height, + creativeId: responseBody.ad_id, + dealId: responseBody.deal || null, + currency: 'USD', + netRevenue: true, + ttl: 1000, + mediaType: BANNER, + ad: responseBody.adm + }); + + return bidResponses; + } +}; +registerBidder(spec); + +function buildUrlParams(bid) { + const host = utils.getTopWindowLocation().host; + const page = utils.getTopWindowLocation().pathname; + const refer = document.referrer; + const language = navigator.language; + const dnt = (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0; + + let queryString = [ + 'ifr', '0', + 'bl', language, + 'je', '1', + 'dnt', dnt, + 'host', host, + 'u', page, + 'ru', refer, + 'adid', utils.getBidIdParameter('adid', bid.params), + 'ver', VER + ]; + + return queryString.reduce( + (memo, curr, index) => + index % 2 === 0 && queryString[index + 1] !== undefined ? memo + curr + '=' + encodeURIComponent(queryString[index + 1]) + '&' : memo, '' + ).slice(0, -1); +} diff --git a/modules/ucfunnelBidAdapter.md b/modules/ucfunnelBidAdapter.md new file mode 100644 index 00000000000..717d2a0089c --- /dev/null +++ b/modules/ucfunnelBidAdapter.md @@ -0,0 +1,30 @@ +# Overview + +``` +Module Name: ucfunnel Bid Adapter +Module Type: Bidder Adapter +Maintainer: ryan.chou@ucfunnel.com +``` + +# Description + +This module connects to ucfunnel's demand sources. It supports display, and rich media formats. +ucfunnel will provide ``adid`` that are specific to your ad type. +Please reach out to ``pr@ucfunnel.com`` to set up an ucfunnel account and above ids. +Use bidder code ```ucfunnel``` for all ucfunnel traffic. + +# Test Parameters + +``` + var adUnits = [ + { + code: 'test-LERC', + sizes: [[300, 250]], + bids: [{ + bidder: 'ucfunnel', + params: { + adid: "test-ad-83444226E44368D1E32E49EEBE6D29" //String - required + } + } + ]; +``` \ No newline at end of file diff --git a/modules/vidazooBidAdapter.js b/modules/vidazooBidAdapter.js new file mode 100644 index 00000000000..981f5603a0a --- /dev/null +++ b/modules/vidazooBidAdapter.js @@ -0,0 +1,128 @@ +import * as utils from 'src/utils'; +import {registerBidder} from 'src/adapters/bidderFactory'; +import {BANNER} from 'src/mediaTypes'; +export const URL = '//prebid.cliipa.com'; +const BIDDER_CODE = 'vidazoo'; +const CURRENCY = 'USD'; +const TTL_SECONDS = 60 * 5; +const INTERNAL_SYNC_TYPE = { + IFRAME: 'iframe', + IMAGE: 'img' +}; +const EXTERNAL_SYNC_TYPE = { + IFRAME: 'iframe', + IMAGE: 'image' +}; + +function isBidRequestValid(bid) { + const params = bid.params || {}; + return !!(params.cId && params.pId); +} + +function buildRequest(bid, topWindowUrl, size) { + const {params, bidId} = bid; + const {bidFloor, cId, pId} = params; + // Prebid's util function returns AppNexus style sizes (i.e. 300x250) + const [width, height] = size.split('x'); + + return { + method: 'GET', + url: `${URL}/prebid/${cId}`, + data: { + url: topWindowUrl, + cb: Date.now(), + bidFloor: bidFloor, + bidId: bidId, + publisherId: pId, + width, + height + } + } +} + +function buildRequests(validBidRequests) { + const topWindowUrl = utils.getTopWindowUrl(); + const requests = []; + validBidRequests.forEach(validBidRequest => { + const sizes = utils.parseSizesInput(validBidRequest.sizes); + sizes.forEach(size => { + const request = buildRequest(validBidRequest, topWindowUrl, size); + requests.push(request); + }); + }); + return requests; +} + +function interpretResponse(serverResponse, request) { + if (!serverResponse || !serverResponse.body) { + return []; + } + const {creativeId, ad, price, exp} = serverResponse.body; + if (!ad || !price) { + return []; + } + const {bidId, width, height} = request.data; + try { + return [{ + requestId: bidId, + cpm: price, + width: width, + height: height, + creativeId: creativeId, + currency: CURRENCY, + netRevenue: true, + ttl: exp || TTL_SECONDS, + ad: ad + }]; + } catch (e) { + return []; + } +} + +function getUserSyncs(syncOptions, responses) { + const {iframeEnabled, pixelEnabled} = syncOptions; + + if (iframeEnabled) { + return [{ + type: 'iframe', + url: '//static.cliipa.com/basev/sync/user_sync.html' + }]; + } + + if (pixelEnabled) { + const lookup = {}; + const syncs = []; + responses.forEach(response => { + const {body} = response; + const cookies = body ? body.cookies || [] : []; + cookies.forEach(cookie => { + switch (cookie.type) { + case INTERNAL_SYNC_TYPE.IFRAME: + break; + case INTERNAL_SYNC_TYPE.IMAGE: + if (pixelEnabled && !lookup[cookie.src]) { + syncs.push({ + type: EXTERNAL_SYNC_TYPE.IMAGE, + url: cookie.src + }); + } + break; + } + }); + }); + return syncs; + } + + return []; +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs +}; + +registerBidder(spec); diff --git a/modules/vidazooBidAdapter.md b/modules/vidazooBidAdapter.md new file mode 100644 index 00000000000..972a99a1445 --- /dev/null +++ b/modules/vidazooBidAdapter.md @@ -0,0 +1,31 @@ +# Overview + +**Module Name:** Vidazoo Bidder Adapter + +**Module Type:** Bidder Adapter + +**Maintainer:** server-dev@getintent.com + +# Description + +Module that connects to Vidazoo's demand sources. + +# Test Parameters +```js +var adUnits = [ + { + code: 'test-ad', + sizes: [[300, 250]], + bids: [ + { + bidder: 'vidazoo', + params: { + cId: '5a1c419d95fce900044c334e', + pId: '59ac17c192832d0011283fe3', + bidFloor: 0.0001 + } + } + ] + } +]; +``` diff --git a/modules/xendizBidAdapter.js b/modules/xendizBidAdapter.js new file mode 100644 index 00000000000..0f1c385a67c --- /dev/null +++ b/modules/xendizBidAdapter.js @@ -0,0 +1,102 @@ +import * as utils from 'src/utils'; +import { registerBidder } from 'src/adapters/bidderFactory'; +import { BANNER } from 'src/mediaTypes'; + +const BIDDER_CODE = 'xendiz'; +const PREBID_ENDPOINT = 'prebid.xendiz.com'; +const SYNC_ENDPOINT = 'https://advsync.com/xendiz/ssp/?pixel=1'; + +const buildURI = () => { + return `//${PREBID_ENDPOINT}/request`; +} + +const getDevice = () => { + const lang = navigator.language || ''; + const width = window.screen.width; + const height = window.screen.height; + + return [lang, width, height]; +}; + +const buildItem = (req) => { + return [ + req.bidId, + req.params, + req.adUnitCode, + req.sizes.map(s => `${s[0]}x${s[1]}`) + ] +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + + /** + * Determines whether or not the given bid request is valid. + * + * @param {object} bid The bid to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function(bid) { + return !!bid.params.pid; + }, + + /** + * Make a server request from the list of BidRequests. + * + * @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) { + const payload = { + id: bidRequests[0].auctionId, + items: bidRequests.map(buildItem), + device: getDevice(), + page: utils.getTopWindowUrl(), + dt: +new Date() + }; + const payloadString = JSON.stringify(payload); + + return { + method: 'POST', + url: buildURI(), + data: payloadString + }; + }, + + /** + * 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) { + const bids = serverResponse.body.bids.map(bid => { + return { + requestId: bid.id, + cpm: bid.price, + width: bid.w, + height: bid.h, + creativeId: bid.crid, + netRevenue: bid.netRevenue !== undefined ? bid.netRevenue : true, + dealId: bid.dealid, + currency: bid.cur || 'USD', + ttl: bid.exp || 900, + ad: bid.adm, + } + }); + + return bids; + }, + + getUserSyncs: function(syncOptions) { + if (syncOptions.pixelEnabled) { + return [{ + type: 'image', + url: SYNC_ENDPOINT + }]; + } + } +} + +registerBidder(spec); diff --git a/modules/xendizBidAdapter.md b/modules/xendizBidAdapter.md new file mode 100644 index 00000000000..4ecabe7070f --- /dev/null +++ b/modules/xendizBidAdapter.md @@ -0,0 +1,41 @@ +# Overview + +Module Name: Xendiz Bidder Adapter +Module Type: Bidder Adapter +Maintainer: hello@xendiz.com + +# Description + +Module that connects to Xendiz demand sources + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + sizes: [[300, 250]], + bids: [ + { + bidder: "xendiz", + params: { + pid: '00000000-0000-0000-0000-000000000000' + } + } + ] + },{ + code: 'test-div', + sizes: [[300, 50]], + bids: [ + { + bidder: "xendiz", + params: { + pid: '00000000-0000-0000-0000-000000000000', + ext: { + uid: '550e8400-e29b-41d4-a716-446655440000' + } + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/modules/yieldlabBidAdapter.js b/modules/yieldlabBidAdapter.js new file mode 100644 index 00000000000..17c205359de --- /dev/null +++ b/modules/yieldlabBidAdapter.js @@ -0,0 +1,107 @@ +import * as utils from 'src/utils' +import { registerBidder } from 'src/adapters/bidderFactory' +import find from 'core-js/library/fn/array/find' +import { VIDEO, BANNER } from 'src/mediaTypes' + +const ENDPOINT = 'https://ad.yieldlab.net' +const BIDDER_CODE = 'yieldlab' +const BID_RESPONSE_TTL_SEC = 600 +const CURRENCY_CODE = 'EUR' + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [VIDEO, BANNER], + + isBidRequestValid: function (bid) { + if (bid && bid.params && bid.params.placementId && bid.params.adSize) { + return true + } + return false + }, + + /** + * This method should build correct URL + * @param validBidRequests + * @returns {{method: string, url: string}} + */ + buildRequests: function (validBidRequests) { + const placementIds = [] + const timestamp = Date.now() + + utils._each(validBidRequests, function (bid) { + placementIds.push(bid.params.placementId) + }) + + const placements = placementIds.join(',') + + return { + method: 'GET', + url: `${ENDPOINT}/yp/${placements}?ts=${timestamp}&json=true`, + validBidRequests: validBidRequests + } + }, + + /** + * Map ad values and pricing and stuff + * @param serverResponse + * @param originalBidRequest + */ + interpretResponse: function (serverResponse, originalBidRequest) { + const bidResponses = [] + const timestamp = Date.now() + + originalBidRequest.validBidRequests.forEach(function (bidRequest) { + if (!serverResponse.body) { + return + } + + let matchedBid = find(serverResponse.body, function (bidResponse) { + return bidRequest.params.placementId == bidResponse.id + }) + + if (matchedBid) { + const sizes = parseSize(bidRequest.params.adSize) + const bidResponse = { + requestId: bidRequest.bidId, + cpm: matchedBid.price / 100, + width: sizes[0], + height: sizes[1], + creativeId: '' + matchedBid.id, + dealId: matchedBid.did, + currency: CURRENCY_CODE, + netRevenue: true, + ttl: BID_RESPONSE_TTL_SEC, + referrer: '', + ad: `` + } + if (isVideo(bidRequest)) { + bidResponse.mediaType = VIDEO + bidResponse.vastUrl = `${ENDPOINT}/d/${matchedBid.id}/${bidRequest.params.accountId}/1x1?ts=${timestamp}` + } + + bidResponses.push(bidResponse) + } + }) + return bidResponses + } +}; + +/** + * Is this a video format? + * @param {String} format + * @returns {Boolean} + */ +function isVideo (format) { + return utils.deepAccess(format, 'mediaTypes.video') +} + +/** + * Expands a 'WxH' string as a 2-element [W, H] array + * @param {String} size + * @returns {Array} + */ +function parseSize (size) { + return size.split('x').map(Number) +} + +registerBidder(spec) diff --git a/modules/yieldlabBidAdapter.md b/modules/yieldlabBidAdapter.md new file mode 100644 index 00000000000..0c9183aa4cd --- /dev/null +++ b/modules/yieldlabBidAdapter.md @@ -0,0 +1,45 @@ +# Overview + +``` +Module Name: Yieldlab Bidder Adapter +Module Type: Bidder Adapter +Maintainer: api@platform-lunar.com +``` + +# Description + +Module that connects to Yieldlab's demand sources + +# Test Parameters +``` + var adUnits = [ + { + code: "test1", + sizes: [[800, 250]] + bids: [{ + bidder: "yieldlab", + params: { + placement: "4206978", + accountId: "2358365", + adSize: "800x250" + } + }] + }, { + code: "test2", + sizes: [[1, 1]], + mediaTypes: { + video: { + context: "instream" + } + }, + bids: [{ + bidder: "yieldlab", + params: { + placementId: "4207034", + accountId: "2358365", + adSize: "1x1" + } + }] + } + ]; +``` diff --git a/modules/yieldmoBidAdapter.js b/modules/yieldmoBidAdapter.js new file mode 100644 index 00000000000..ad2aff7dec8 --- /dev/null +++ b/modules/yieldmoBidAdapter.js @@ -0,0 +1,304 @@ +import * as utils from 'src/utils'; +import { registerBidder } from 'src/adapters/bidderFactory'; + +const BIDDER_CODE = 'yieldmo'; +const CURRENCY = 'USD'; +const TIME_TO_LIVE = 300; +const NET_REVENUE = true; +const SYNC_ENDPOINT = 'https://static.yieldmo.com/blank.min.html?orig='; +const SERVER_ENDPOINT = 'https://ads.yieldmo.com/exchange/prebid'; +const localWindow = getTopWindow(); + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: ['banner'], + /** + * Determines whether or not the given bid request is valid. + * @param {object} bid, bid to validate + * @return boolean, true if valid, otherwise false + */ + isBidRequestValid: function(bid) { + return !!(bid && bid.adUnitCode && bid.bidId); + }, + /** + * Make a server request from the list of BidRequests. + * + * @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) { + let serverRequest = { + p: [], + page_url: utils.getTopWindowUrl(), + bust: new Date().getTime().toString(), + pr: utils.getTopWindowReferrer(), + scrd: localWindow.devicePixelRatio || 0, + dnt: getDNT(), + e: getEnvironment(), + description: getPageDescription(), + title: localWindow.document.title || '', + w: localWindow.innerWidth, + h: localWindow.innerHeight + }; + bidRequests.forEach((request) => { + serverRequest.p.push(addPlacement(request)); + }); + serverRequest.p = '[' + serverRequest.p.toString() + ']'; + return { + method: 'GET', + url: SERVER_ENDPOINT, + data: serverRequest + } + }, + /** + * Makes Yieldmo Ad Server response compatible to Prebid specs + * @param serverResponse successful response from Ad Server + * @param bidderRequest original bidRequest + * @return {Bid[]} an array of bids + */ + interpretResponse: function(serverResponse) { + let bids = []; + let data = serverResponse.body; + if (data.length > 0) { + data.forEach((response) => { + if (response.cpm && response.cpm > 0) { + bids.push(createNewBid(response)); + } + }); + } + return bids; + }, + getUserSync: function(syncOptions) { + if (trackingEnabled(syncOptions)) { + return [{ + type: 'iframe', + url: SYNC_ENDPOINT + utils.getOrigin() + }]; + } else { + return []; + } + } +} +registerBidder(spec); + +/*************************************** + * Helper Functions + ***************************************/ + +/** + * Adds placement information to array + * @param request bid request + */ +function addPlacement(request) { + const placementInfo = { + placement_id: request.adUnitCode, + callback_id: request.bidId, + sizes: request.sizes + } + if (request.params && request.params.placementId) { + placementInfo.ym_placement_id = request.params.placementId + } + return JSON.stringify(placementInfo); +} + +/** + * creates a new bid with response information + * @param response server response + */ +function createNewBid(response) { + return { + requestId: response['callback_id'], + cpm: response.cpm, + width: response.width, + height: response.height, + creativeId: response.creativeId, + currency: CURRENCY, + netRevenue: NET_REVENUE, + ttl: TIME_TO_LIVE, + ad: response.ad + }; +} + +/** + * Detects if tracking is allowed + * @returns false if dnt or if not iframe/pixel enabled + */ +function trackingEnabled(options) { + return (isIOS() && !getDNT() && options.iframeEnabled); +} + +/** + * Detects whether we're in iOS + * @returns true if in iOS + */ +function isIOS() { + return /iPhone|iPad|iPod/i.test(window.navigator.userAgent); +} + +/** + * Detects whether dnt is true + * @returns true if user enabled dnt + */ +function getDNT() { + return window.doNotTrack === '1' || window.navigator.doNotTrack === '1' || false; +} + +/** + * get page description + */ +function getPageDescription() { + if (document.querySelector('meta[name="description"]')) { + return document.querySelector('meta[name="description"]').getAttribute('content'); // Value of the description metadata from the publisher's page. + } else { + return ''; + } +} + +function getTopWindow() { + try { + return window.top; + } catch (e) { + return window; + } +} + +/*************************************** + * Detect Environment Helper Functions + ***************************************/ + +/** + * Represents a method for loading Yieldmo ads. Environments affect + * which formats can be loaded into the page + * Environments: + * CodeOnPage: 0, // div directly on publisher's page + * Amp: 1, // google Accelerate Mobile Pages ampproject.org + * Mraid = 2, // native loaded through the MRAID spec, without Yieldmo's SDK https://www.iab.net/media/file/IAB_MRAID_v2_FINAL.pdf + * Dfp: 4, // google doubleclick for publishers https://www.doubleclickbygoogle.com/ + * DfpInAmp: 5, // AMP page containing a DFP iframe + * SafeFrame: 10, + * DfpSafeFrame: 11,Sandboxed: 16, // An iframe that can't get to the top window. + * SuperSandboxed: 89, // An iframe without allow-same-origin + * Unknown: 90, // A default sandboxed implementation delivered by EnvironmentDispatch when all positive environment checks fail + */ + +/** + * Detects what environment we're in + * @returns Environment kind + */ +function getEnvironment() { + if (isSuperSandboxedIframe()) { + return 89; + } else if (isDfpInAmp()) { + return 5; + } else if (isDfp()) { + return 4; + } else if (isAmp()) { + return 1; + } else if (isDFPSafeFrame()) { + return 11; + } else if (isSafeFrame()) { + return 10; + } else if (isMraid()) { + return 2; + } else if (isCodeOnPage()) { + return 0; + } else if (isSandboxedIframe()) { + return 16; + } else { + return 90; + } +} + +/** + * @returns true if we are running on the top window at dispatch time + */ +function isCodeOnPage() { + return window === window.parent; +} + +/** + * @returns true if the environment is both DFP and AMP + */ +function isDfpInAmp() { + return isDfp() && isAmp(); +} + +/** + * @returns true if the window is in an iframe whose id and parent element id match DFP + */ +function isDfp() { + try { + const frameElement = window.frameElement; + const parentElement = window.frameElement.parentNode; + if (frameElement && parentElement) { + return frameElement.id.indexOf('google_ads_iframe') > -1 && parentElement.id.indexOf('google_ads_iframe') > -1; + } + return false; + } catch (e) { + return false; + } +} + +/** +* @returns true if there is an AMP context object +*/ +function isAmp() { + try { + const ampContext = window.context || window.parent.context; + if (ampContext && ampContext.pageViewId) { + return ampContext; + } + return false; + } catch (e) { + return false; + } +} + +/** + * @returns true if the environment is a SafeFrame. + */ +function isSafeFrame() { + return window.$sf && window.$sf.ext; +} + +/** + * @returns true if the environment is a dfp safe frame. + */ +function isDFPSafeFrame() { + if (window.location && window.location.href) { + const href = window.location.href; + return isSafeFrame() && href.indexOf('google') !== -1 && href.indexOf('safeframe') !== -1; + } + return false; +} + +/** + * Return true if we are in an iframe and can't access the top window. + */ +function isSandboxedIframe() { + return window.top !== window && !window.frameElement; +} + +/** + * Return true if we cannot document.write to a child iframe (this implies no allow-same-origin) + */ +function isSuperSandboxedIframe() { + const sacrificialIframe = window.document.createElement('iframe'); + try { + sacrificialIframe.setAttribute('style', 'display:none'); + window.document.body.appendChild(sacrificialIframe); + sacrificialIframe.contentWindow._testVar = true; + window.document.body.removeChild(sacrificialIframe); + return false; + } catch (e) { + window.document.body.removeChild(sacrificialIframe); + return true; + } +} + +/** + * @returns true if the window has the attribute identifying MRAID + */ +function isMraid() { + return !!(window.mraid); +} diff --git a/modules/yieldmoBidAdapter.md b/modules/yieldmoBidAdapter.md new file mode 100644 index 00000000000..8c60202d7ea --- /dev/null +++ b/modules/yieldmoBidAdapter.md @@ -0,0 +1,31 @@ +# Overview + +``` +Module Name: Yieldmo Bid Adapter +Module Type: Bidder Adapter +Maintainer: opensource@yieldmo.com +Note: Our ads will only render in mobile +``` + +# Description + +Connects to Yieldmo Ad Server for bids. + +Yieldmo bid adapter supports Banner. + +# Test Parameters +``` +var adUnits = [ + // Banner adUnit + { + code: 'div-gpt-ad-1460505748561-0', + sizes: [[300, 250], [300,600]], + bids: [{ + bidder: 'yieldmo', + params: { + placementId: '1779781193098233305' // string with at most 19 characters (may include numbers only) + } + }] + } +]; +``` \ No newline at end of file diff --git a/package.json b/package.json index fdd66001fed..839b6113ff1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "1.2.0", + "version": "1.4.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { diff --git a/src/auction.js b/src/auction.js index 7a6b44ca459..0af6458ac09 100644 --- a/src/auction.js +++ b/src/auction.js @@ -209,6 +209,44 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels}) } } +function doCallbacksIfTimedout(auctionInstance, bidResponse) { + if (bidResponse.timeToRespond > auctionInstance.getTimeout() + config.getConfig('timeoutBuffer')) { + auctionInstance.executeCallback(true); + } +} + +// Add a bid to the auction. +function addBidToAuction(auctionInstance, bidResponse) { + events.emit(CONSTANTS.EVENTS.BID_RESPONSE, bidResponse); + auctionInstance.addBidReceived(bidResponse); + + doCallbacksIfTimedout(auctionInstance, bidResponse); +} + +// Video bids may fail if the cache is down, or there's trouble on the network. +function tryAddVideoBid(auctionInstance, bidResponse, bidRequest, vastUrl) { + if (config.getConfig('cache.url')) { + store([bidResponse], function(error, cacheIds) { + if (error) { + utils.logWarn(`Failed to save to the video cache: ${error}. Video bid must be discarded.`); + + doCallbacksIfTimedout(auctionInstance, bidResponse); + } else { + bidResponse.videoCacheKey = cacheIds[0].uuid; + if (!vastUrl) { + bidResponse.vastUrl = getCacheUrl(bidResponse.videoCacheKey); + } + // only set this prop after the bid has been cached to avoid early ending auction early in bidsBackAll + bidRequest.doneCbCallCount += 1; + addBidToAuction(auctionInstance, bidResponse); + auctionInstance.bidsBackAll(); + } + }); + } else { + addBidToAuction(auctionInstance, bidResponse); + } +} + export const addBidResponse = createHook('asyncSeries', function(adUnitCode, bid) { let auctionInstance = this; let bidRequests = auctionInstance.getBidRequests(); @@ -218,46 +256,9 @@ export const addBidResponse = createHook('asyncSeries', function(adUnitCode, bid let bidResponse = getPreparedBidForAuction({adUnitCode, bid, bidRequest, auctionId}); if (bidResponse.mediaType === 'video') { - tryAddVideoBid(bidResponse); + tryAddVideoBid(auctionInstance, bidResponse, bidRequest, bid.vastUrl); } else { - doCallbacksIfNeeded(); - addBidToAuction(bidResponse); - } - - function doCallbacksIfNeeded() { - if (bidResponse.timeToRespond > auctionInstance.getTimeout() + config.getConfig('timeoutBuffer')) { - auctionInstance.executeCallback(true); - } - } - - // Add a bid to the auction. - function addBidToAuction() { - events.emit(CONSTANTS.EVENTS.BID_RESPONSE, bidResponse); - auctionInstance.addBidReceived(bidResponse); - } - - // Video bids may fail if the cache is down, or there's trouble on the network. - function tryAddVideoBid(bidResponse) { - if (config.getConfig('cache.url')) { - store([bidResponse], function(error, cacheIds) { - if (error) { - utils.logWarn(`Failed to save to the video cache: ${error}. Video bid must be discarded.`); - } else { - bidResponse.videoCacheKey = cacheIds[0].uuid; - if (!bid.vastUrl) { - bidResponse.vastUrl = getCacheUrl(bidResponse.videoCacheKey); - } - // only set this prop after the bid has been cached to avoid early ending auction early in bidsBackAll - bidRequest.doneCbCallCount += 1; - addBidToAuction(bidResponse); - auctionInstance.bidsBackAll(); - } - doCallbacksIfNeeded(); - }); - } else { - addBidToAuction(bidResponse); - doCallbacksIfNeeded(); - } + addBidToAuction(auctionInstance, bidResponse); } }, 'addBidResponse'); @@ -287,7 +288,7 @@ function getPreparedBidForAuction({adUnitCode, bid, bidRequest, auctionId}) { const adUnitRenderer = bidRequest.bids && bidRequest.bids[0] && bidRequest.bids[0].renderer; - if (adUnitRenderer) { + if (adUnitRenderer && adUnitRenderer.url) { bidObject.renderer = Renderer.install({ url: adUnitRenderer.url }); bidObject.renderer.setRender(adUnitRenderer.render); } @@ -397,7 +398,7 @@ export function getKeyValueTargetingPairs(bidderCode, custBidObj) { } // set native key value targeting - if (custBidObj.native) { + if (custBidObj['native']) { keyValues = Object.assign({}, keyValues, getNativeTargeting(custBidObj)); } diff --git a/src/hook.js b/src/hook.js index 5ba1d4b9bbf..6c6cefdc56c 100644 --- a/src/hook.js +++ b/src/hook.js @@ -68,7 +68,7 @@ export function createHook(type, fn, hookName) { } function hookedFn(...args) { - if (_hooks.length === 0) { + if (_hooks.length === 1 && _hooks[0].fn === fn) { return fn.apply(this, args); } return types[type].apply(this, args); diff --git a/src/prebid.js b/src/prebid.js index 86fd678ee7e..0225b32191b 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -13,7 +13,7 @@ import includes from 'core-js/library/fn/array/includes'; var $$PREBID_GLOBAL$$ = getGlobal(); -var CONSTANTS = require('./constants.json'); +const CONSTANTS = require('./constants.json'); var utils = require('./utils.js'); var adaptermanager = require('./adaptermanager'); var bidfactory = require('./bidfactory'); @@ -23,9 +23,7 @@ const { triggerUserSyncs } = userSync; /* private variables */ const RENDERED = 'rendered'; -var BID_WON = CONSTANTS.EVENTS.BID_WON; -var SET_TARGETING = CONSTANTS.EVENTS.SET_TARGETING; -var ADD_AD_UNITS = CONSTANTS.EVENTS.ADD_AD_UNITS; +const { ADD_AD_UNITS, BID_WON, REQUEST_BIDS, SET_TARGETING } = CONSTANTS.EVENTS; var eventValidators = { bidWon: checkDefinedPlacement @@ -283,7 +281,7 @@ $$PREBID_GLOBAL$$.removeAdUnit = function (adUnitCode) { * @alias module:pbjs.requestBids */ $$PREBID_GLOBAL$$.requestBids = createHook('asyncSeries', function ({ bidsBackHandler, timeout, adUnits, adUnitCodes, labels } = {}) { - events.emit('requestBids'); + events.emit(REQUEST_BIDS); const cbTimeout = timeout || config.getConfig('bidderTimeout'); adUnits = adUnits || $$PREBID_GLOBAL$$.adUnits; diff --git a/src/utils.js b/src/utils.js index 0957b4e6c8f..8735f918497 100644 --- a/src/utils.js +++ b/src/utils.js @@ -809,6 +809,13 @@ export function getOrigin() { } } +/** + * Returns Do Not Track state + */ +export function getDNT() { + return navigator.doNotTrack === '1' || window.doNotTrack === '1' || navigator.msDoNotTrack === '1' || navigator.doNotTrack === 'yes'; +} + const compareCodeAndSlot = (slot, adUnitCode) => slot.getAdUnitPath() === adUnitCode || slot.getSlotElementId() === adUnitCode; /** diff --git a/test/spec/auctionmanager_spec.js b/test/spec/auctionmanager_spec.js index a1248030c51..c73679c5b8c 100644 --- a/test/spec/auctionmanager_spec.js +++ b/test/spec/auctionmanager_spec.js @@ -466,6 +466,7 @@ describe('auctionmanager.js', function () { let auction; let ajaxStub; const BIDDER_CODE = 'sampleBidder'; + const BIDDER_CODE1 = 'sampleBidder1'; let makeRequestsStub; let bids = [{ 'ad': 'creative', @@ -656,57 +657,6 @@ describe('auctionmanager.js', function () { assert.equal(addedBid.renderer.url, 'renderer.js'); }); }); - - describe('with auction timeout 20', () => { - let auction; - let adUnits; - let adUnitCodes; - let createAuctionStub; - let spec; - let getBidderRequestStub; - let eventsEmitSpy; - - beforeEach(() => { - adUnits = [{ - code: 'adUnit-code', - bids: [ - {bidder: BIDDER_CODE, params: {placementId: 'id'}}, - ] - }]; - adUnitCodes = ['adUnit-code']; - auction = auctionModule.newAuction({adUnits, adUnitCodes, callback: function() {}, cbTimeout: 20}); - createAuctionStub = sinon.stub(auctionModule, 'newAuction'); - createAuctionStub.returns(auction); - getBidderRequestStub = sinon.stub(utils, 'getBidderRequest'); - - let newBidRequest = Object.assign({}, bidRequests[0], {'start': 1000}); - getBidderRequestStub.returns(newBidRequest); - - spec = { - code: BIDDER_CODE, - isBidRequestValid: sinon.stub(), - buildRequests: sinon.stub(), - interpretResponse: sinon.stub(), - getUserSyncs: sinon.stub() - }; - eventsEmitSpy = sinon.spy(events, 'emit'); - }); - - afterEach(() => { - auctionModule.newAuction.restore(); - utils.getBidderRequest.restore(); - events.emit.restore(); - }); - - it('should emit BID_TIMEOUT for timed out bids', () => { - registerBidder(spec); - spec.buildRequests.returns([{'id': 123, 'method': 'POST'}]); - spec.isBidRequestValid.returns(true); - spec.interpretResponse.returns(bids); - auction.callBids(); - assert.ok(eventsEmitSpy.calledWith(CONSTANTS.EVENTS.BID_TIMEOUT), 'emitted events BID_TIMEOUT'); - }); - }); }); describe('addBidResponse', () => { diff --git a/test/spec/modules/33acrossBidAdapter_spec.js b/test/spec/modules/33acrossBidAdapter_spec.js index 0b2d65e7db9..9a322821b13 100644 --- a/test/spec/modules/33acrossBidAdapter_spec.js +++ b/test/spec/modules/33acrossBidAdapter_spec.js @@ -2,7 +2,6 @@ const { userSync } = require('../../../src/userSync'); const { config } = require('../../../src/config'); const { expect } = require('chai'); - const { isBidRequestValid, buildRequests, @@ -44,78 +43,71 @@ describe('33acrossBidAdapter:', function () { }); describe('isBidRequestValid:', function () { - context('valid bid request:', function () { - it('returns true when bidder, params.siteId, params.productId are set', function() { - const validBid = { - bidder: BIDDER_CODE, - params: { - siteId: SITE_ID, - productId: PRODUCT_ID - } + it('returns true when valid bid request is sent', function() { + const validBid = { + bidder: BIDDER_CODE, + params: { + siteId: SITE_ID, + productId: PRODUCT_ID } + } - expect(isBidRequestValid(validBid)).to.be.true; - }) + expect(isBidRequestValid(validBid)).to.be.true; }); - context('valid test bid request:', function () { - it('returns true when bidder, params.site.id, params.productId are set', function() { - const validBid = { - bidder: BIDDER_CODE, - params: { - site: { - id: SITE_ID - }, - productId: PRODUCT_ID - } + it('returns true when valid test bid request is sent', function() { + const validBid = { + bidder: BIDDER_CODE, + params: { + siteId: SITE_ID, + productId: PRODUCT_ID, + test: 1 } + } - expect(isBidRequestValid(validBid)).to.be.true; - }); + expect(isBidRequestValid(validBid)).to.be.true; }); - context('invalid bid request:', function () { - it('returns false when bidder not set to "33across"', function () { - const invalidBid = { - bidder: 'foo', - params: { - siteId: SITE_ID, - productId: PRODUCT_ID - } + it('returns false when bidder not set to "33across"', function () { + const invalidBid = { + bidder: 'foo', + params: { + siteId: SITE_ID, + productId: PRODUCT_ID } + } - expect(isBidRequestValid(invalidBid)).to.be.false; - }); + expect(isBidRequestValid(invalidBid)).to.be.false; + }); - it('returns false when params not set', function() { - const invalidBid = { - bidder: 'foo' - } + it('returns false when params not set', function() { + const invalidBid = { + bidder: 'foo' + } - expect(isBidRequestValid(invalidBid)).to.be.false; - }); + expect(isBidRequestValid(invalidBid)).to.be.false; + }); - it('returns false when params.siteId or params.site.id not set', function() { - const invalidBid = { - bidder: 'foo', - params: { - productId: PRODUCT_ID - } + it('returns false when site ID is not set in params', function() { + const invalidBid = { + bidder: 'foo', + params: { + productId: PRODUCT_ID } + } - expect(isBidRequestValid(invalidBid)).to.be.false; - }); + expect(isBidRequestValid(invalidBid)).to.be.false; + }); - it('returns false when params.productId not set', function() { - const invalidBid = { - bidder: 'foo', - params: { - siteId: SITE_ID - } + it('returns false when product ID not set in params', function() { + const invalidBid = { + bidder: 'foo', + params: { + siteId: SITE_ID } + } - expect(isBidRequestValid(invalidBid)).to.be.false; - }); + expect(isBidRequestValid(invalidBid)).to.be.false; }); }); @@ -148,27 +140,76 @@ describe('33acrossBidAdapter:', function () { }, id: 'b1' }; + const serverRequest = { + 'method': 'POST', + 'url': END_POINT, + 'data': JSON.stringify(ttxRequest), + 'options': { + 'contentType': 'application/json', + 'withCredentials': false + } + } + const builtServerRequests = buildRequests(this.bidRequests); + expect(builtServerRequests).to.deep.equal([ serverRequest ]); + expect(builtServerRequests.length).to.equal(1); + }); + + it('returns corresponding test server requests for each valid bidRequest', function() { + this.sandbox.stub(config, 'getConfig', () => { + return { + 'url': 'https://foo.com/hb/' + } + }); + + const ttxRequest = { + imp: [ { + banner: { + format: [ + { + w: 300, + h: 250, + ext: { } + }, + { + w: 728, + h: 90, + ext: { } + } + ] + }, + ext: { + ttx: { + prod: PRODUCT_ID + } + } + } ], + site: { + id: SITE_ID + }, + id: 'b1' + }; const serverRequest = { method: 'POST', - url: END_POINT, + url: 'https://foo.com/hb/', data: JSON.stringify(ttxRequest), options: { contentType: 'application/json', withCredentials: false } - } + }; + const builtServerRequests = buildRequests(this.bidRequests); expect(builtServerRequests).to.deep.equal([ serverRequest ]); expect(builtServerRequests.length).to.equal(1); }); - it('returns corresponding test server requests for each valid bidRequest', function() { + it('returns corresponding test server requests for each valid test bidRequest', function() { this.sandbox.stub(config, 'getConfig', () => { return { 'url': 'https://foo.com/hb/' } }); - + this.bidRequests[0].params.test = 1; const ttxRequest = { imp: [ { banner: { @@ -176,12 +217,12 @@ describe('33acrossBidAdapter:', function () { { w: 300, h: 250, - ext: {} + ext: { } }, { w: 728, h: 90, - ext: {} + ext: { } } ] }, @@ -194,7 +235,8 @@ describe('33acrossBidAdapter:', function () { site: { id: SITE_ID }, - id: 'b1' + id: 'b1', + test: 1 }; const serverRequest = { method: 'POST', @@ -268,11 +310,7 @@ describe('33acrossBidAdapter:', function () { bid: [ { id: '1', adm: '

I am an ad

', - ext: { - rp: { - advid: 1 - } - }, + crid: 1, h: 250, w: 300, price: 0.0938 @@ -322,11 +360,7 @@ describe('33acrossBidAdapter:', function () { bid: [ { id: '1', adm: '

I am an ad

', - ext: { - rp: { - advid: 1 - } - }, + crid: 1, h: 250, w: 300, price: 0.0940 @@ -334,11 +368,7 @@ describe('33acrossBidAdapter:', function () { { id: '2', adm: '

I am an ad

', - ext: { - rp: { - advid: 2 - } - }, + crid: 2, h: 250, w: 300, price: 0.0938 @@ -349,11 +379,7 @@ describe('33acrossBidAdapter:', function () { bid: [ { id: '3', adm: '

I am an ad

', - ext: { - rp: { - advid: 3 - } - }, + crid: 3, h: 250, w: 300, price: 0.0938 @@ -373,7 +399,7 @@ describe('33acrossBidAdapter:', function () { creativeId: 1, currency: 'USD', netRevenue: true - } + }; expect(interpretResponse({ body: serverResponse }, this.serverRequest)).to.deep.equal([ bidResponse ]); }); diff --git a/test/spec/modules/adformBidAdapter_spec.js b/test/spec/modules/adformBidAdapter_spec.js index 72f625d08c6..357e6e67f4d 100644 --- a/test/spec/modules/adformBidAdapter_spec.js +++ b/test/spec/modules/adformBidAdapter_spec.js @@ -22,7 +22,7 @@ describe('Adform adapter', () => { adxDomain: 'adx.adform.net' }; assert.isFalse(spec.isBidRequestValid(bid)); - }) + }); }); describe('buildRequests', () => { @@ -147,6 +147,7 @@ describe('Adform adapter', () => { bids = [ { adUnitCode: placementCode[0], + auctionId: '7aefb970-2045', bidId: '2a0cf4e', bidder: 'adform', bidderRequestId: '1ab8d9', @@ -154,29 +155,28 @@ describe('Adform adapter', () => { adxDomain: 'newDomain', tid: 45, placementCode: placementCode[0], - requestId: '7aefb970-2045', sizes: [[300, 250], [250, 300], [300, 600], [600, 300]], transactionId: '5f33781f-9552-4ca1' }, { adUnitCode: placementCode[1], + auctionId: '7aefb970-2045', bidId: '2a0cf5b', bidder: 'adform', bidderRequestId: '1ab8d9', params: params[1], placementCode: placementCode[1], - requestId: '7aefb970-2045', sizes: [[300, 250], [250, 300], [300, 600], [600, 300]], transactionId: '5f33781f-9552-4iuy' }, { adUnitCode: placementCode[2], + auctionId: '7aefb970-2045', bidId: '2a0cf6n', bidder: 'adform', bidderRequestId: '1ab8d9', params: params[2], placementCode: placementCode[2], - requestId: '7aefb970-2045', sizes: [[300, 250], [250, 300], [300, 600], [600, 300]], transactionId: '5f33781f-9552-7ev3' } diff --git a/test/spec/modules/adgenerationBidAdapter_spec.js b/test/spec/modules/adgenerationBidAdapter_spec.js new file mode 100644 index 00000000000..7718d6fabac --- /dev/null +++ b/test/spec/modules/adgenerationBidAdapter_spec.js @@ -0,0 +1,333 @@ +import {expect} from 'chai'; +import * as utils from 'src/utils'; +import {spec} from 'modules/adgenerationBidAdapter'; +import {newBidder} from 'src/adapters/bidderFactory'; +import {NATIVE} from 'src/mediaTypes'; + +describe('AdgenerationAdapter', () => { + const adapter = newBidder(spec); + const ENDPOINT = ['http://api-test.scaleout.jp/adsv/v1', 'https://d.socdm.com/adsv/v1']; + + describe('inherited functions', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', () => { + let bid = { + 'bidder': 'adg', + 'params': { + id: '58278', // banner + } + }; + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', () => { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', () => { + const bidRequests = [ + { // banner + bidder: 'adg', + params: { + id: '58278', + width: '300', + height: '250' + }, + adUnitCode: 'adunit-code', + sizes: [[300, 250]], + bidId: '2f6ac468a9c15e', + bidderRequestId: '14a9f773e30243', + auctionId: '4aae9f05-18c6-4fcd-80cf-282708cd584a', + transactionTd: 'f76f6dfd-d64f-4645-a29f-682bac7f431a' + }, + { // native + bidder: 'adg', + params: { + id: '58278', + width: '300', + height: '250' + }, + mediaTypes: { + native: { + image: { + required: true + }, + title: { + required: true, + len: 80 + }, + sponsoredBy: { + required: true + }, + clickUrl: { + required: true + }, + body: { + required: true + }, + icon: { + required: true + } + }, + }, + adUnitCode: 'adunit-code', + sizes: [[1, 1]], + bidId: '2f6ac468a9c15e', + bidderRequestId: '14a9f773e30243', + auctionId: '4aae9f05-18c6-4fcd-80cf-282708cd584a', + transactionTd: 'f76f6dfd-d64f-4645-a29f-682bac7f431a' + } + ]; + const data = { + banner: 'posall=SSPLOC&id=58278&sdktype=0&hb=true&t=json3&imark=1', + native: 'posall=SSPLOC&id=58278&sdktype=0&hb=true&t=json3' + }; + it('sends bid request to ENDPOINT via GET', () => { + const request = spec.buildRequests(bidRequests)[0]; + expect(request.url).to.equal(ENDPOINT[1]); + expect(request.method).to.equal('GET'); + }); + + it('sends bid request to debug ENDPOINT via GET', () => { + bidRequests[0].params.debug = true; + const request = spec.buildRequests(bidRequests)[0]; + expect(request.url).to.equal(ENDPOINT[0]); + expect(request.method).to.equal('GET'); + }); + + it('should attache params to the banner request', () => { + const request = spec.buildRequests(bidRequests)[0]; + expect(request.data).to.equal(data.banner); + }); + + it('should attache params to the native request', () => { + const request = spec.buildRequests(bidRequests)[1]; + expect(request.data).to.equal(data.native); + }); + }); + + describe('interpretResponse', () => { + const bidRequests = { + bidRequest: { + bidder: 'adg', + params: { + id: '58278', // banner + }, + mediaTypes: { + native: { + image: { + required: true + }, + title: { + required: true, + len: 80 + }, + sponsoredBy: { + required: true + }, + clickUrl: { + required: true + }, + body: { + required: true + }, + icon: { + required: true + } + } + }, + adUnitCode: 'adunit-code', + sizes: [[1, 1]], + bidId: '2f6ac468a9c15e', + bidderRequestId: '14a9f773e30243', + auctionId: '4aae9f05-18c6-4fcd-80cf-282708cd584a', + transactionTd: 'f76f6dfd-d64f-4645-a29f-682bac7f431a' + } + }; + + const serverResponse = { + ad: '↵ ↵ ↵ ↵ ↵
↵ ', + beacon: '', + cpm: 36.0008, + displaytype: '1', + ids: {}, + location_params: null, + locationid: '58279', + native_ad: { + assets: [ + { + data: { + label: 'accompanying_text', + value: 'AD' + }, + id: 501 + }, + { + data: { + label: 'optout_url', + value: 'https://supership.jp/optout/' + }, + id: 502 + }, + { + data: { + ext: { + black_back: 'https://i.socdm.com/sdk/img/icon_adg_optout_26x26_white.png', + }, + label: 'information_icon_url', + value: 'https://i.socdm.com/sdk/img/icon_adg_optout_26x26_gray.png', + id: 503 + } + }, + { + id: 1, + required: 1, + title: {text: 'Title'} + }, + { + id: 2, + img: { + h: 250, + url: 'https://s3-ap-northeast-1.amazonaws.com/sdk-temp/adg-sample-ad/img/300x250.png', + w: 300 + }, + required: 1 + }, + { + id: 3, + img: { + h: 300, + url: 'https://placehold.jp/300x300.png', + w: 300 + }, + required: 1 + }, + { + data: {value: 'Description'}, + id: 5, + required: 0 + }, + { + data: {value: 'CTA'}, + id: 6, + required: 0 + }, + { + data: {value: 'Sponsored'}, + id: 4, + required: 0 + } + ], + imptrackers: ['https://s3-ap-northeast-1.amazonaws.com/adg-dummy-dsp/1x1.gif'], + link: { + clicktrackers: [ + 'https://s3-ap-northeast-1.amazonaws.com/adg-dummy-dsp/1x1_clicktracker_access.gif' + ], + url: 'https://supership.jp' + }, + }, + rotation: '0', + scheduleid: '512603', + sdktype: '0', + creativeid: '1k2kv35vsa5r', + dealid: 'fd5sa5fa7f', + ttl: 1000 + }; + + const bidResponses = [ + { + requestId: '2f6ac468a9c15e', + cpm: 36.0008, + width: 1, + height: 1, + creativeId: '1k2kv35vsa5r', + dealId: 'fd5sa5fa7f', + currency: 'JPY', + netRevenue: true, + ttl: 1000, + referrer: utils.getTopWindowUrl(), + ad: '↵
', + native: { + title: 'Title', + image: { + url: 'https://s3-ap-northeast-1.amazonaws.com/sdk-temp/adg-sample-ad/img/300x250.png', + height: 250, + width: 300 + }, + icon: { + url: 'https://placehold.jp/300x300.png', + height: 300, + width: 300 + }, + sponsoredBy: 'Sponsored', + body: 'Description', + cta: 'CTA', + clickUrl: 'https://supership.jp', + clickTrackers: ['https://s3-ap-northeast-1.amazonaws.com/adg-dummy-dsp/1x1_clicktracker_access.gif'], + impressionTrackers: ['https://s3-ap-northeast-1.amazonaws.com/adg-dummy-dsp/1x1.gif'] + }, + mediaType: NATIVE + } + ]; + + it('no bid responses', () => { + const result = spec.interpretResponse({body: serverResponse}, bidRequests); + expect(result.length).to.equal(0); + }); + + it('handles native responses', () => { + serverResponse.results = [{ad: 'Creative<\/body>'}]; + const result = spec.interpretResponse({body: serverResponse}, bidRequests)[0]; + expect(result.requestId).to.equal(bidResponses[0].requestId); + expect(result.width).to.equal(bidResponses[0].width); + expect(result.height).to.equal(bidResponses[0].height); + expect(result.creativeId).to.equal(bidResponses[0].creativeId); + expect(result.dealId).to.equal(bidResponses[0].dealId); + expect(result.currency).to.equal(bidResponses[0].currency); + expect(result.netRevenue).to.equal(bidResponses[0].netRevenue); + expect(result.ttl).to.equal(bidResponses[0].ttl); + expect(result.referrer).to.equal(bidResponses[0].referrer); + expect(result.native.title).to.equal(bidResponses[0].native.title); + expect(result.native.image.url).to.equal(bidResponses[0].native.image.url); + expect(result.native.image.height).to.equal(bidResponses[0].native.image.height); + expect(result.native.image.width).to.equal(bidResponses[0].native.image.width); + expect(result.native.icon.url).to.equal(bidResponses[0].native.icon.url); + expect(result.native.icon.width).to.equal(bidResponses[0].native.icon.width); + expect(result.native.icon.height).to.equal(bidResponses[0].native.icon.height); + expect(result.native.sponsoredBy).to.equal(bidResponses[0].native.sponsoredBy); + expect(result.native.body).to.equal(bidResponses[0].native.body); + expect(result.native.cta).to.equal(bidResponses[0].native.cta); + expect(result.native.clickUrl).to.equal(bidResponses[0].native.clickUrl); + expect(result.native.impressionTrackers[0]).to.equal(bidResponses[0].native.impressionTrackers[0]); + expect(result.native.clickTrackers[0]).to.equal(bidResponses[0].native.clickTrackers[0]); + expect(result.mediaType).to.equal(bidResponses[0].mediaType); + }); + + it('handles banner responses', () => { + serverResponse.results = [{ad: '↵ ↵ ↵ ↵ ↵
↵ '}]; + delete serverResponse.native_ad; + delete serverResponse.mediaType; + delete bidRequests.bidRequest.mediaTypes; + const result = spec.interpretResponse({body: serverResponse}, bidRequests)[0]; + expect(result.requestId).to.equal(bidResponses[0].requestId); + expect(result.width).to.equal(bidResponses[0].width); + expect(result.height).to.equal(bidResponses[0].height); + expect(result.creativeId).to.equal(bidResponses[0].creativeId); + expect(result.dealId).to.equal(bidResponses[0].dealId); + expect(result.currency).to.equal(bidResponses[0].currency); + expect(result.netRevenue).to.equal(bidResponses[0].netRevenue); + expect(result.ttl).to.equal(bidResponses[0].ttl); + expect(result.referrer).to.equal(bidResponses[0].referrer); + expect(result.ad).to.equal(bidResponses[0].ad); + }); + }); +}); diff --git a/test/spec/modules/adkernelBidAdapter_spec.js b/test/spec/modules/adkernelBidAdapter_spec.js index b958e96f656..717027ca4ee 100644 --- a/test/spec/modules/adkernelBidAdapter_spec.js +++ b/test/spec/modules/adkernelBidAdapter_spec.js @@ -8,7 +8,7 @@ describe('Adkernel adapter', () => { bidId: 'Bid_01', params: {zoneId: 1, host: 'rtb.adkernel.com'}, placementCode: 'ad-unit-1', - sizes: [[300, 250]] + sizes: [[300, 250], [300, 200]] }, bid2_zone2 = { bidder: 'adkernel', bidId: 'Bid_02', @@ -63,7 +63,9 @@ describe('Adkernel adapter', () => { crid: '100_001', price: 3.01, nurl: 'https://rtb.com/win?i=ZjKoPYSFI3Y_0', - adm: '' + adm: '', + w: 300, + h: 250 }] }], cur: 'USD', @@ -78,7 +80,9 @@ describe('Adkernel adapter', () => { impid: 'Bid_02', crid: '100_002', price: 1.31, - adm: '' + adm: '', + w: 300, + h: 250 }] }], cur: 'USD' @@ -125,21 +129,19 @@ describe('Adkernel adapter', () => { describe('banner request building', () => { let bidRequest; - let mock; - before(() => { - mock = sinon.stub(utils, 'getTopWindowLocation', () => { - return { - protocol: 'https:', - hostname: 'example.com', - host: 'example.com', - pathname: '/index.html', - href: 'https://example.com/index.html' - }; - }); + let wmock = sinon.stub(utils, 'getTopWindowLocation', () => ({ + protocol: 'https:', + hostname: 'example.com', + host: 'example.com', + pathname: '/index.html', + href: 'https://example.com/index.html' + })); + let dntmock = sinon.stub(utils, 'getDNT', () => true); let request = spec.buildRequests([bid1_zone1])[0]; bidRequest = JSON.parse(request.data.r); - mock.restore(); + wmock.restore(); + dntmock.restore(); }); it('should be a first-price auction', () => { @@ -150,9 +152,9 @@ describe('Adkernel adapter', () => { expect(bidRequest.imp[0]).to.have.property('banner'); }); - it('should have h/w', () => { - expect(bidRequest.imp[0].banner).to.have.property('w', 300); - expect(bidRequest.imp[0].banner).to.have.property('h', 250); + it('should have w/h', () => { + expect(bidRequest.imp[0].banner).to.have.property('format'); + expect(bidRequest.imp[0].banner.format).to.be.eql([{w: 300, h: 250}, {w: 300, h: 200}]); }); it('should respect secure connection', () => { @@ -172,7 +174,8 @@ describe('Adkernel adapter', () => { expect(bidRequest).to.have.property('device'); expect(bidRequest.device).to.have.property('ip', 'caller'); expect(bidRequest.device).to.have.property('ua', 'caller'); - }) + expect(bidRequest.device).to.have.property('dnt', 1); + }); }); describe('video request building', () => { diff --git a/test/spec/modules/appnexusBidAdapter_spec.js b/test/spec/modules/appnexusBidAdapter_spec.js index 6b09690d17a..560fcf40acc 100644 --- a/test/spec/modules/appnexusBidAdapter_spec.js +++ b/test/spec/modules/appnexusBidAdapter_spec.js @@ -1,6 +1,7 @@ import { expect } from 'chai'; import { spec } from 'modules/appnexusBidAdapter'; import { newBidder } from 'src/adapters/bidderFactory'; +import { deepClone } from 'src/utils'; const ENDPOINT = '//ib.adnxs.com/ut/v3/prebid'; @@ -392,7 +393,7 @@ describe('AppNexusAdapter', () => { }); it('handles native responses', () => { - let response1 = Object.assign({}, response); + let response1 = deepClone(response); response1.tags[0].ads[0].ad_type = 'native'; response1.tags[0].ads[0].rtb.native = { 'title': 'Native Creative', @@ -424,5 +425,26 @@ describe('AppNexusAdapter', () => { expect(result[0].native.cta).to.equal('Do it'); expect(result[0].native.image.url).to.equal('http://cdn.adnxs.com/img.png'); }); + + it('supports configuring outstream renderers', () => { + const outstreamResponse = deepClone(response); + outstreamResponse.tags[0].ads[0].rtb.video = {}; + outstreamResponse.tags[0].ads[0].renderer_url = 'renderer.js'; + + const bidderRequest = { + bids: [{ + renderer: { + options: { + adText: 'configured' + } + } + }] + }; + + const result = spec.interpretResponse({ body: outstreamResponse }, {bidderRequest}); + expect(result[0].renderer.config).to.deep.equal( + bidderRequest.bids[0].renderer.options + ); + }); }); }); diff --git a/test/spec/modules/audienceNetworkBidAdapter_spec.js b/test/spec/modules/audienceNetworkBidAdapter_spec.js index cabcdce46f2..6b21eb459d4 100644 --- a/test/spec/modules/audienceNetworkBidAdapter_spec.js +++ b/test/spec/modules/audienceNetworkBidAdapter_spec.js @@ -390,5 +390,43 @@ describe('AudienceNetwork adapter', () => { expect(bidResponseNative.height).to.equal(250); expect(bidResponseNative.ad).to.contain(`placementid:'${nativePlacementId}',format:'native',bidid:'${nativeBidId}'`); }); + + it('mixture of valid native bid and error in response', () => { + const [bidResponse] = interpretResponse({ + body: { + errors: ['test-error-message'], + bids: { + [placementId]: [{ + placement_id: placementId, + bid_id: 'test-bid-id', + bid_price_cents: 123, + bid_price_currency: 'usd', + bid_price_model: 'cpm' + }] + } + } + }, { + adformats: ['native'], + requestIds: [requestId], + sizes: [[300, 250]] + }); + + expect(bidResponse.cpm).to.equal(1.23); + expect(bidResponse.requestId).to.equal(requestId); + expect(bidResponse.width).to.equal(300); + expect(bidResponse.height).to.equal(250); + expect(bidResponse.ad) + .to.contain(`placementid:'${placementId}',format:'native',bidid:'test-bid-id'`, 'ad missing parameters') + .and.to.contain('getElementsByTagName("style")', 'ad missing native styles') + .and.to.contain('
', 'ad missing native container'); + expect(bidResponse.creativeId).to.equal(placementId); + expect(bidResponse.netRevenue).to.equal(true); + expect(bidResponse.currency).to.equal('USD'); + + expect(bidResponse.hb_bidder).to.equal('fan'); + expect(bidResponse.fb_bidid).to.equal('test-bid-id'); + expect(bidResponse.fb_format).to.equal('native'); + expect(bidResponse.fb_placementid).to.equal(placementId); + }); }); }); diff --git a/test/spec/modules/c1xBidAdapter_spec.js b/test/spec/modules/c1xBidAdapter_spec.js new file mode 100644 index 00000000000..0517668ea0d --- /dev/null +++ b/test/spec/modules/c1xBidAdapter_spec.js @@ -0,0 +1,164 @@ +import { expect } from 'chai'; +import { c1xAdapter } from 'modules/c1xBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; + +const ENDPOINT = 'https://ht.c1exchange.com/ht'; +const BIDDER_CODE = 'c1x'; + +describe('C1XAdapter', () => { + const adapter = newBidder(c1xAdapter); + + describe('inherited functions', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', () => { + let bid = { + 'bidder': BIDDER_CODE, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'params': { + 'siteId': '9999' + } + }; + + it('should return true when required params are passed', () => { + expect(c1xAdapter.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not found', () => { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'siteId': null + }; + expect(c1xAdapter.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', () => { + let bidRequests = [ + { + 'bidder': BIDDER_CODE, + 'params': { + 'siteId': '9999' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + } + ]; + + const parseRequest = (data) => { + const parsedData = '{"' + data.replace(/=|&/g, (foundChar) => { + if (foundChar == '=') return '":"'; + else if (foundChar == '&') return '","'; + }) + '"}' + return parsedData; + }; + + it('sends bid request to ENDPOINT via GET', () => { + const request = c1xAdapter.buildRequests(bidRequests); + expect(request.url).to.equal(ENDPOINT); + expect(request.method).to.equal('GET'); + }); + + it('should generate correct bid Id tag', () => { + const request = c1xAdapter.buildRequests(bidRequests); + expect(request.bids[0].adUnitCode).to.equal('adunit-code'); + expect(request.bids[0].bidId).to.equal('30b31c1838de1e'); + }); + + it('should convert params to proper form and attach to request', () => { + const request = c1xAdapter.buildRequests(bidRequests); + const originalPayload = parseRequest(request.data); + const payloadObj = JSON.parse(originalPayload); + expect(payloadObj.adunits).to.equal('1'); + expect(payloadObj.a1s).to.equal('300x250,300x600'); + expect(payloadObj.a1).to.equal('adunit-code'); + expect(payloadObj.site).to.equal('9999'); + }); + + it('should convert floor price to proper form and attach to request', () => { + let bidRequest = Object.assign({}, + bidRequests[0], + { + 'params': { + 'siteId': '9999', + 'floorPriceMap': { + '300x250': 4.35 + } + } + }); + const request = c1xAdapter.buildRequests([bidRequest]); + const originalPayload = parseRequest(request.data); + const payloadObj = JSON.parse(originalPayload); + expect(payloadObj.a1p).to.equal('4.35'); + }); + + it('should convert pageurl to proper form and attach to request', () => { + let bidRequest = Object.assign({}, + bidRequests[0], + { + 'params': { + 'siteId': '9999', + 'pageurl': 'http://c1exchange.com/' + } + }); + const request = c1xAdapter.buildRequests([bidRequest]); + const originalPayload = parseRequest(request.data); + const payloadObj = JSON.parse(originalPayload); + expect(payloadObj.pageurl).to.equal('http://c1exchange.com/'); + }); + }); + + describe('interpretResponse', () => { + let response = { + 'bid': true, + 'cpm': 1.5, + 'ad': '', + 'width': 300, + 'height': 250, + 'crid': '8888', + 'adId': 'c1x-test', + 'bidType': 'GROSS_BID' + }; + + it('should get correct bid response', () => { + let expectedResponse = [ + { + width: 300, + height: 250, + cpm: 1.5, + ad: '', + creativeId: '8888', + currency: 'USD', + ttl: 300, + netRevenue: false, + requestId: 'yyyy' + } + ]; + let bidderRequest = {}; + bidderRequest.bids = [ + { adUnitCode: 'c1x-test', + bidId: 'yyyy' } + ]; + let result = c1xAdapter.interpretResponse({ body: [response] }, bidderRequest); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + }); + + it('handles nobid responses', () => { + let response = { + bid: false, + adId: 'c1x-test' + }; + let bidderRequest = {}; + let result = c1xAdapter.interpretResponse({ body: [response] }, bidderRequest); + expect(result.length).to.equal(0); + }); + }); +}); diff --git a/test/spec/modules/danmarketplaceBidAdapter_spec.js b/test/spec/modules/danmarketplaceBidAdapter_spec.js new file mode 100644 index 00000000000..ee7f844ae98 --- /dev/null +++ b/test/spec/modules/danmarketplaceBidAdapter_spec.js @@ -0,0 +1,285 @@ +import { expect } from 'chai'; +import { spec } from 'modules/danmarketplaceBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; + +describe('DAN_Marketplace Adapter', function () { + const adapter = newBidder(spec); + + describe('inherited functions', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', () => { + let bid = { + 'bidder': 'danmarketplace', + 'params': { + 'uid': '4' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', () => { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'uid': 0 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', () => { + let bidRequests = [ + { + 'bidder': 'danmarketplace', + 'params': { + 'uid': '5' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }, + { + 'bidder': 'danmarketplace', + 'params': { + 'uid': '5' + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[728, 90]], + 'bidId': '3150ccb55da321', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }, + { + 'bidder': 'danmarketplace', + 'params': { + 'uid': '6' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '42dbe3a7168a6a', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + } + ]; + + it('should attach valid params to the tag', () => { + const request = spec.buildRequests([bidRequests[0]]); + const payload = request.data; + expect(payload).to.be.an('object'); + expect(payload).to.have.property('u').that.is.a('string'); + expect(payload).to.have.property('pt', 'net'); + expect(payload).to.have.property('auids', '5'); + }); + + it('auids must not be duplicated', () => { + const request = spec.buildRequests(bidRequests); + const payload = request.data; + expect(payload).to.be.an('object'); + expect(payload).to.have.property('u').that.is.a('string'); + expect(payload).to.have.property('pt', 'net'); + expect(payload).to.have.property('auids', '5,6'); + }); + + it('pt parameter must be "gross" if params.priceType === "gross"', () => { + bidRequests[1].params.priceType = 'gross'; + const request = spec.buildRequests(bidRequests); + const payload = request.data; + expect(payload).to.be.an('object'); + expect(payload).to.have.property('u').that.is.a('string'); + expect(payload).to.have.property('pt', 'gross'); + expect(payload).to.have.property('auids', '5,6'); + delete bidRequests[1].params.priceType; + }); + + it('pt parameter must be "net" or "gross"', () => { + bidRequests[1].params.priceType = 'some'; + const request = spec.buildRequests(bidRequests); + const payload = request.data; + expect(payload).to.be.an('object'); + expect(payload).to.have.property('u').that.is.a('string'); + expect(payload).to.have.property('pt', 'net'); + expect(payload).to.have.property('auids', '5,6'); + delete bidRequests[1].params.priceType; + }); + }); + + describe('interpretResponse', () => { + const responses = [ + {'bid': [{'price': 1.15, 'adm': '
test content 1
', 'auid': 4, 'h': 250, 'w': 300}], 'seat': '1'}, + {'bid': [{'price': 0.5, 'adm': '
test content 2
', 'auid': 5, 'h': 90, 'w': 728}], 'seat': '1'}, + {'bid': [{'price': 0, 'auid': 6, 'h': 250, 'w': 300}], 'seat': '1'}, + {'bid': [{'price': 0, 'adm': '
test content 4
', 'h': 250, 'w': 300}], 'seat': '1'}, + undefined, + {'bid': [], 'seat': '1'}, + {'seat': '1'}, + ]; + + it('should get correct bid response', () => { + const bidRequests = [ + { + 'bidder': 'danmarketplace', + 'params': { + 'uid': '4' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '659423fff799cb', + 'bidderRequestId': '5f2009617a7c0a', + 'auctionId': '1cbd2feafe5e8b', + } + ]; + const request = spec.buildRequests(bidRequests); + const expectedResponse = [ + { + 'requestId': '659423fff799cb', + 'cpm': 1.15, + 'creativeId': 4, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '
test content 1
', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 360, + } + ]; + + const result = spec.interpretResponse({'body': {'seatbid': [responses[0]]}}, request); + expect(result).to.deep.equal(expectedResponse); + }); + + it('should get correct multi bid response', () => { + const bidRequests = [ + { + 'bidder': 'danmarketplace', + 'params': { + 'uid': '4' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '300bfeb0d71a5b', + 'bidderRequestId': '2c2bb1972df9a', + 'auctionId': '1fa09aee5c8c99', + }, + { + 'bidder': 'danmarketplace', + 'params': { + 'uid': '5' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '4dff80cc4ee346', + 'bidderRequestId': '2c2bb1972df9a', + 'auctionId': '1fa09aee5c8c99', + }, + { + 'bidder': 'danmarketplace', + 'params': { + 'uid': '4' + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[728, 90]], + 'bidId': '5703af74d0472a', + 'bidderRequestId': '2c2bb1972df9a', + 'auctionId': '1fa09aee5c8c99', + } + ]; + const request = spec.buildRequests(bidRequests); + const expectedResponse = [ + { + 'requestId': '300bfeb0d71a5b', + 'cpm': 1.15, + 'creativeId': 4, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '
test content 1
', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 360, + }, + { + 'requestId': '5703af74d0472a', + 'cpm': 1.15, + 'creativeId': 4, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '
test content 1
', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 360, + }, + { + 'requestId': '4dff80cc4ee346', + 'cpm': 0.5, + 'creativeId': 5, + 'dealId': undefined, + 'width': 728, + 'height': 90, + 'ad': '
test content 2
', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 360, + } + ]; + + const result = spec.interpretResponse({'body': {'seatbid': [responses[0], responses[1]]}}, request); + expect(result).to.deep.equal(expectedResponse); + }); + + it('handles wrong and nobid responses', () => { + const bidRequests = [ + { + 'bidder': 'danmarketplace', + 'params': { + 'uid': '6' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '300bfeb0d7190gf', + 'bidderRequestId': '2c2bb1972d23af', + 'auctionId': '1fa09aee5c84d34', + }, + { + 'bidder': 'danmarketplace', + 'params': { + 'uid': '7' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '300bfeb0d71321', + 'bidderRequestId': '2c2bb1972d23af', + 'auctionId': '1fa09aee5c84d34', + }, + { + 'bidder': 'danmarketplace', + 'params': { + 'uid': '8' + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[728, 90]], + 'bidId': '300bfeb0d7183bb', + 'bidderRequestId': '2c2bb1972d23af', + 'auctionId': '1fa09aee5c84d34', + } + ]; + const request = spec.buildRequests(bidRequests); + const result = spec.interpretResponse({'body': {'seatbid': responses.slice(2)}}, request); + expect(result.length).to.equal(0); + }); + }); +}); diff --git a/test/spec/modules/iasBidAdapter_spec.js b/test/spec/modules/iasBidAdapter_spec.js new file mode 100644 index 00000000000..4f335ab22ba --- /dev/null +++ b/test/spec/modules/iasBidAdapter_spec.js @@ -0,0 +1,190 @@ +import { expect } from 'chai'; +import { spec } from 'modules/iasBidAdapter'; + +describe('iasBidAdapter is an adapter that', () => { + it('has the correct bidder code', () => { + expect(spec.code).to.equal('ias'); + }); + describe('has a method `isBidRequestValid` that', () => { + it('exists', () => { + expect(spec.isBidRequestValid).to.be.a('function'); + }); + it('returns false if bid params misses `pubId`', () => { + expect(spec.isBidRequestValid( + { + params: { + adUnitPath: 'someAdUnitPath' + } + })).to.equal(false); + }); + it('returns false if bid params misses `adUnitPath`', () => { + expect(spec.isBidRequestValid( + { + params: { + pubId: 'somePubId' + } + })).to.equal(false); + }); + it('returns true otherwise', () => { + expect(spec.isBidRequestValid( + { + params: { + adUnitPath: 'someAdUnitPath', + pubId: 'somePubId', + someOtherParam: 'abc' + } + })).to.equal(true); + }); + }); + + describe('has a method `buildRequests` that', () => { + it('exists', () => { + expect(spec.buildRequests).to.be.a('function'); + }); + describe('given bid requests, returns a `ServerRequest` instance that', () => { + let bidRequests, IAS_HOST; + beforeEach(() => { + IAS_HOST = '//pixel.adsafeprotected.com/services/pub'; + bidRequests = [ + { + adUnitCode: 'one-div-id', + auctionId: 'someAuctionId', + bidId: 'someBidId', + bidder: 'ias', + bidderRequestId: 'someBidderRequestId', + params: { + pubId: '1234', + adUnitPath: '/a/b/c' + }, + sizes: [ + [10, 20], + [300, 400] + ], + transactionId: 'someTransactionId' + }, + { + adUnitCode: 'two-div-id', + auctionId: 'someAuctionId', + bidId: 'someBidId', + bidder: 'ias', + bidderRequestId: 'someBidderRequestId', + params: { + pubId: '1234', + adUnitPath: '/d/e/f' + }, + sizes: [ + [50, 60] + ], + transactionId: 'someTransactionId' + } + ]; + }); + it('has property `method` of `GET`', () => { + expect(spec.buildRequests(bidRequests)).to.deep.include({ + method: 'GET' + }); + }); + it('has property `url` to be the correct IAS endpoint', () => { + expect(spec.buildRequests(bidRequests)).to.deep.include({ + url: IAS_HOST + }); + }); + describe('has property `data` that is an encode query string containing information such as', () => { + let val; + const ANID_PARAM = 'anId'; + const SLOT_PARAM = 'slot'; + const SLOT_ID_PARAM = 'id'; + const SLOT_SIZE_PARAM = 'ss'; + const SLOT_AD_UNIT_PATH_PARAM = 'p'; + + beforeEach(() => val = decodeURI(spec.buildRequests(bidRequests).data)); + it('publisher id', () => { + expect(val).to.have.string(`${ANID_PARAM}=1234`); + }); + it('ad slot`s id, size and ad unit path', () => { + expect(val).to.have.string(`${SLOT_PARAM}={${SLOT_ID_PARAM}:one-div-id,${SLOT_SIZE_PARAM}:[10.20,300.400],${SLOT_AD_UNIT_PATH_PARAM}:/a/b/c}`); + expect(val).to.have.string(`${SLOT_PARAM}={${SLOT_ID_PARAM}:two-div-id,${SLOT_SIZE_PARAM}:[50.60],${SLOT_AD_UNIT_PATH_PARAM}:/d/e/f}`); + }); + it('window size', () => { + expect(val).to.match(/.*wr=[0-9]*\.[0-9]*/); + }); + it('screen size', () => { + expect(val).to.match(/.*sr=[0-9]*\.[0-9]*/); + }); + }); + it('has property `bidRequest` that is the first passed in bid request', () => { + expect(spec.buildRequests(bidRequests)).to.deep.include({ + bidRequest: bidRequests[0] + }); + }); + }); + }); + describe('has a method `interpretResponse` that', () => { + it('exists', () => { + expect(spec.interpretResponse).to.be.a('function'); + }); + describe('returns a list of bid response that', () => { + let bidResponse, slots; + beforeEach(() => { + const request = { + bidRequest: { + bidId: '102938' + } + }; + slots = {}; + slots['test-div-id'] = { + id: '1234', + vw: ['60', '70'] + }; + slots['test-div-id-two'] = { + id: '5678', + vw: ['80', '90'] + }; + const serverResponse = { + body: { + brandSafety: { + adt: 'adtVal', + alc: 'alcVal', + dlm: 'dlmVal', + drg: 'drgVal', + hat: 'hatVal', + off: 'offVal', + vio: 'vioVal' + }, + fr: 'false', + slots: slots + }, + headers: {} + }; + bidResponse = spec.interpretResponse(serverResponse, request); + }); + it('has IAS keyword `adt` as property', () => { + expect(bidResponse[0]).to.deep.include({ adt: 'adtVal' }); + }); + it('has IAS keyword `alc` as property', () => { + expect(bidResponse[0]).to.deep.include({ alc: 'alcVal' }); + }); + it('has IAS keyword `dlm` as property', () => { + expect(bidResponse[0]).to.deep.include({ dlm: 'dlmVal' }); + }); + it('has IAS keyword `drg` as property', () => { + expect(bidResponse[0]).to.deep.include({ drg: 'drgVal' }); + }); + it('has IAS keyword `hat` as property', () => { + expect(bidResponse[0]).to.deep.include({ hat: 'hatVal' }); + }); + it('has IAS keyword `off` as property', () => { + expect(bidResponse[0]).to.deep.include({ off: 'offVal' }); + }); + it('has IAS keyword `vio` as property', () => { + expect(bidResponse[0]).to.deep.include({ vio: 'vioVal' }); + }); + it('has IAS keyword `fr` as property', () => { + expect(bidResponse[0]).to.deep.include({ fr: 'false' }); + }); + it('has property `slots`', () => { + expect(bidResponse[0]).to.deep.include({ slots: slots }); + }); + }); + }); +}); diff --git a/test/spec/modules/improvedigitalBidAdapter_spec.js b/test/spec/modules/improvedigitalBidAdapter_spec.js index 3f93a62e850..2725a6ad101 100644 --- a/test/spec/modules/improvedigitalBidAdapter_spec.js +++ b/test/spec/modules/improvedigitalBidAdapter_spec.js @@ -1,5 +1,6 @@ import { expect } from 'chai'; import { ImproveDigitalAdServerJSClient, spec } from 'modules/improvedigitalBidAdapter'; +import { config } from 'src/config'; import { userSync } from 'src/userSync'; describe('Improve Digital Adapter Tests', function () { @@ -129,6 +130,15 @@ describe('Improve Digital Adapter Tests', function () { expect(params.bid_request.imp[0].banner).to.deep.equal(size); }); + it('should add currency', () => { + const bidRequest = Object.assign({}, simpleBidRequest); + const getConfigStub = sinon.stub(config, 'getConfig').returns('JPY'); + const request = spec.buildRequests([bidRequest])[0]; + const params = JSON.parse(request.data.substring(PARAM_PREFIX.length)); + expect(params.bid_request.imp[0].currency).to.equal('JPY'); + getConfigStub.restore(); + }); + it('should return 2 requests', () => { const requests = spec.buildRequests([ simpleBidRequest, diff --git a/test/spec/modules/nanointeractiveBidAdapter_spec.js b/test/spec/modules/nanointeractiveBidAdapter_spec.js index 4b498c88982..14927d929d3 100644 --- a/test/spec/modules/nanointeractiveBidAdapter_spec.js +++ b/test/spec/modules/nanointeractiveBidAdapter_spec.js @@ -2,8 +2,7 @@ import { expect } from 'chai'; import * as utils from 'src/utils'; import { - ALG, - BIDDER_CODE, CATEGORY, DATA_PARTNER_ID, DATA_PARTNER_PIXEL_ID, ENGINE_BASE_URL, NQ, NQ_NAME, SECURITY, + BIDDER_CODE, CATEGORY, DATA_PARTNER_PIXEL_ID, ENGINE_BASE_URL, NQ, NQ_NAME, spec } from '../../../modules/nanointeractiveBidAdapter'; @@ -20,10 +19,7 @@ describe('nanointeractive adapter tests', function () { bidder: BIDDER_CODE, params: (function () { return { - [SECURITY]: isValid === true ? 'sec1' : null, - [DATA_PARTNER_ID]: 'dpid1', - [DATA_PARTNER_PIXEL_ID]: 'pid1', - [ALG]: 'ihr', + [DATA_PARTNER_PIXEL_ID]: isValid === true ? 'pid1' : null, [NQ]: SEARCH_QUERY, [NQ_NAME]: null, [CATEGORY]: null, @@ -38,11 +34,8 @@ describe('nanointeractive adapter tests', function () { } } - const SINGlE_BID_REQUEST = { - [SECURITY]: 'sec1', - [DATA_PARTNER_ID]: 'dpid1', + const SINGLE_BID_REQUEST = { [DATA_PARTNER_PIXEL_ID]: 'pid1', - [ALG]: 'ihr', [NQ]: [SEARCH_QUERY, null], sizes: [WIDTH + 'x' + HEIGHT], bidId: '24a1c9ec270973', @@ -91,16 +84,16 @@ describe('nanointeractive adapter tests', function () { let request = nanoBidAdapter.buildRequests([getBid(true)]); expect(request.method).to.equal('POST'); expect(request.url).to.equal(ENGINE_BASE_URL); - expect(request.data).to.equal(JSON.stringify([SINGlE_BID_REQUEST])); + expect(request.data).to.equal(JSON.stringify([SINGLE_BID_REQUEST])); stub.restore(); }); it('Test interpretResponse() length', function () { - let bids = nanoBidAdapter.interpretResponse([getSingleBidResponse(true), getSingleBidResponse(false)]); + let bids = nanoBidAdapter.interpretResponse({body: [getSingleBidResponse(true), getSingleBidResponse(false)]}); expect(bids.length).to.equal(1); }); it('Test interpretResponse() bids', function () { - let bid = nanoBidAdapter.interpretResponse([getSingleBidResponse(true), getSingleBidResponse(false)])[0]; + let bid = nanoBidAdapter.interpretResponse({body: [getSingleBidResponse(true), getSingleBidResponse(false)]})[0]; expect(bid.requestId).to.equal(VALID_BID.requestId); expect(bid.cpm).to.equal(VALID_BID.cpm); expect(bid.width).to.equal(VALID_BID.width); diff --git a/test/spec/modules/optimaticBidAdapter_spec.js b/test/spec/modules/optimaticBidAdapter_spec.js index 890c0b78613..d701d981f37 100644 --- a/test/spec/modules/optimaticBidAdapter_spec.js +++ b/test/spec/modules/optimaticBidAdapter_spec.js @@ -106,7 +106,7 @@ describe('OptimaticBidAdapter', () => { expect(bidResponse.length).to.equal(0); }); - it('should return no bids if the response "adm" is missing', () => { + it('should return no bids if the response "nurl" and "adm" are missing', () => { const serverResponse = {seatbid: [{bid: [{price: 5.01}]}]}; const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); expect(bidResponse.length).to.equal(0); @@ -118,7 +118,7 @@ describe('OptimaticBidAdapter', () => { expect(bidResponse.length).to.equal(0); }); - it('should return a valid bid response', () => { + it('should return a valid bid response with just "adm"', () => { const serverResponse = {seatbid: [{bid: [{id: 1, price: 5.01, adm: ''}]}], cur: 'USD'}; const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); let o = { @@ -136,5 +136,43 @@ describe('OptimaticBidAdapter', () => { }; expect(bidResponse).to.deep.equal(o); }); + + it('should return a valid bid response with just "nurl"', () => { + const serverResponse = {seatbid: [{bid: [{id: 1, price: 5.01, nurl: 'https://mg-bid-win.optimatic.com/win/134eb262-948a-463e-ad93-bc8b622d399c?wp=${AUCTION_PRICE}'}]}], cur: 'USD'}; + const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); + let o = { + requestId: bidRequest.bidId, + bidderCode: spec.code, + cpm: serverResponse.seatbid[0].bid[0].price, + creativeId: serverResponse.seatbid[0].bid[0].id, + vastUrl: serverResponse.seatbid[0].bid[0].nurl, + width: 640, + height: 480, + mediaType: 'video', + currency: 'USD', + ttl: 300, + netRevenue: true + }; + expect(bidResponse).to.deep.equal(o); + }); + + it('should return a valid bid response with "nurl" when both nurl and adm exist', () => { + const serverResponse = {seatbid: [{bid: [{id: 1, price: 5.01, adm: '', nurl: 'https://mg-bid-win.optimatic.com/win/134eb262-948a-463e-ad93-bc8b622d399c?wp=${AUCTION_PRICE}'}]}], cur: 'USD'}; + const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); + let o = { + requestId: bidRequest.bidId, + bidderCode: spec.code, + cpm: serverResponse.seatbid[0].bid[0].price, + creativeId: serverResponse.seatbid[0].bid[0].id, + vastUrl: serverResponse.seatbid[0].bid[0].nurl, + width: 640, + height: 480, + mediaType: 'video', + currency: 'USD', + ttl: 300, + netRevenue: true + }; + expect(bidResponse).to.deep.equal(o); + }); }); }); diff --git a/test/spec/modules/peak226BidAdapter_spec.js b/test/spec/modules/peak226BidAdapter_spec.js new file mode 100644 index 00000000000..37bbc1b67bd --- /dev/null +++ b/test/spec/modules/peak226BidAdapter_spec.js @@ -0,0 +1,114 @@ +import { expect } from 'chai'; +import { spec } from 'modules/peak226BidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; + +const URL = 'a.ad216.com/header_bid'; + +describe('PeakAdapter', () => { + const adapter = newBidder(spec); + + describe('isBidRequestValid', () => { + it('should return true when required params found', () => { + const bid = { + params: { + uid: 123 + } + }; + + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', () => { + const bid = { + params: {} + }; + + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + xdescribe('buildRequests', () => { + const bidRequests = [ + { + params: { + uid: '1234' + } + } + ]; + + it('sends bid request to URL via GET', () => { + const request = spec.buildRequests(bidRequests); + + expect(request.url).to.equal(`${URL}?uids=1234`); + expect(request.method).to.equal('GET'); + }); + }); + + describe('interpretResponse', () => { + it('should handle empty response', () => { + let bids = spec.interpretResponse( + {}, + { + bidsMap: {} + } + ); + + expect(bids).to.be.lengthOf(0); + }); + + it('should handle no seatbid returned', () => { + let response = {}; + + let bids = spec.interpretResponse( + { body: response }, + { + bidsMap: {} + } + ); + + expect(bids).to.be.lengthOf(0); + }); + + it('should handle empty seatbid returned', () => { + let response = { seatbid: [] }; + + let bids = spec.interpretResponse( + { body: response }, + { + bidsMap: {} + } + ); + + expect(bids).to.be.lengthOf(0); + }); + + it('should handle seatbid returned bids', () => { + const bidsMap = { 1: [{ bidId: 11 }] }; + const bid = { + price: 0.2, + auid: 1, + h: 250, + w: 300, + adm: 'content' + }; + const response = { + seatbid: [ + { + seat: 'foo', + bid: [bid] + } + ] + }; + + let bids = spec.interpretResponse({ body: response }, { bidsMap }); + + expect(bids).to.be.lengthOf(1); + + expect(bids[0].cpm).to.equal(bid.price); + expect(bids[0].width).to.equal(bid.w); + expect(bids[0].height).to.equal(bid.h); + expect(bids[0].ad).to.equal(bid.adm); + expect(bids[0].bidderCode).to.equal(spec.code); + }); + }); +}); diff --git a/test/spec/modules/platformioBidAdapter_spec.js b/test/spec/modules/platformioBidAdapter_spec.js index d775229c9e3..7caca2e0d17 100644 --- a/test/spec/modules/platformioBidAdapter_spec.js +++ b/test/spec/modules/platformioBidAdapter_spec.js @@ -1,14 +1,14 @@ import {expect} from 'chai'; import {spec} from 'modules/platformioBidAdapter'; import {getTopWindowLocation} from 'src/utils'; +import {newBidder} from 'src/adapters/bidderFactory'; -describe('Platformio Adapter Tests', () => { +describe('Platform.io Adapter Tests', () => { const slotConfigs = [{ placementCode: '/DfpAccount1/slot1', - sizes: [[300, 250]], bidId: 'bid12345', params: { - pubId: '28082', + pubId: '29521', siteId: '26047', placementId: '123', size: '300x250', @@ -16,15 +16,32 @@ describe('Platformio Adapter Tests', () => { } }, { placementCode: '/DfpAccount2/slot2', - sizes: [[250, 250]], bidId: 'bid23456', params: { - pubId: '28082', + pubId: '29521', siteId: '26047', - placementId: '456', - size: '250x250' + placementId: '1234', + size: '728x90', + bidFloor: '0.000001' } }]; + const nativeSlotConfig = [{ + placementCode: '/DfpAccount1/slot3', + bidId: 'bid12345', + nativeParams: { + title: { required: true, len: 200 }, + body: {}, + image: { wmin: 100 }, + sponsoredBy: { }, + icon: { } + }, + params: { + pubId: '29521', + placementId: '123', + siteId: '26047' + } + }]; + it('Verify build request', () => { const request = spec.buildRequests(slotConfigs); expect(request.url).to.equal('//piohbdisp.hb.adx1.com/'); @@ -33,7 +50,7 @@ describe('Platformio Adapter Tests', () => { // site object expect(ortbRequest.site).to.not.equal(null); expect(ortbRequest.site.publisher).to.not.equal(null); - expect(ortbRequest.site.publisher.id).to.equal('28082'); + expect(ortbRequest.site.publisher.id).to.equal('29521'); expect(ortbRequest.site.ref).to.equal(window.top.document.referrer); expect(ortbRequest.site.page).to.equal(getTopWindowLocation().href); expect(ortbRequest.imp).to.have.lengthOf(2); @@ -47,10 +64,10 @@ describe('Platformio Adapter Tests', () => { expect(ortbRequest.imp[0].banner.h).to.equal(250); expect(ortbRequest.imp[0].bidfloor).to.equal('0.001'); // slot 2 - expect(ortbRequest.imp[1].tagid).to.equal('456'); + expect(ortbRequest.imp[1].tagid).to.equal('1234'); expect(ortbRequest.imp[1].banner).to.not.equal(null); - expect(ortbRequest.imp[1].banner.w).to.equal(250); - expect(ortbRequest.imp[1].banner.h).to.equal(250); + expect(ortbRequest.imp[1].banner.w).to.equal(728); + expect(ortbRequest.imp[1].banner.h).to.equal(90); expect(ortbRequest.imp[1].bidfloor).to.equal('0.000001'); }); @@ -62,8 +79,7 @@ describe('Platformio Adapter Tests', () => { bid: [{ impid: ortbRequest.imp[0].id, price: 1.25, - adm: 'This is an Ad', - adid: '471810', + adm: 'This is an Ad' }] }], cur: 'USD' @@ -76,7 +92,9 @@ describe('Platformio Adapter Tests', () => { expect(bid.ad).to.equal('This is an Ad'); expect(bid.width).to.equal(300); expect(bid.height).to.equal(250); - expect(bid.creativeId).to.equal('471810'); + expect(bid.adId).to.equal('bid12345'); + expect(bid.creativeId).to.equal('bid12345'); + expect(bid.netRevenue).to.equal(true); expect(bid.currency).to.equal('USD'); expect(bid.ttl).to.equal(360); }); @@ -87,13 +105,101 @@ describe('Platformio Adapter Tests', () => { expect(bids).to.have.lengthOf(0); }); + it('Verify Native request', () => { + const request = spec.buildRequests(nativeSlotConfig); + expect(request.url).to.equal('//piohbdisp.hb.adx1.com/'); + expect(request.method).to.equal('POST'); + const ortbRequest = JSON.parse(request.data); + // // native impression + expect(ortbRequest.imp[0].tagid).to.equal('123'); + const nativePart = ortbRequest.imp[0]['native']; + expect(nativePart).to.not.equal(null); + expect(nativePart.ver).to.equal('1.1'); + expect(nativePart.request).to.not.equal(null); + // native request assets + const nativeRequest = JSON.parse(ortbRequest.imp[0]['native'].request); + expect(nativeRequest).to.not.equal(null); + expect(nativeRequest.assets).to.have.lengthOf(5); + expect(nativeRequest.assets[0].id).to.equal(1); + expect(nativeRequest.assets[1].id).to.equal(2); + expect(nativeRequest.assets[2].id).to.equal(3); + expect(nativeRequest.assets[3].id).to.equal(4); + expect(nativeRequest.assets[4].id).to.equal(5); + expect(nativeRequest.assets[0].required).to.equal(1); + expect(nativeRequest.assets[0].title).to.not.equal(null); + expect(nativeRequest.assets[0].title.len).to.equal(200); + expect(nativeRequest.assets[1].title).to.be.undefined; + expect(nativeRequest.assets[1].data).to.not.equal(null); + expect(nativeRequest.assets[1].data.type).to.equal(2); + expect(nativeRequest.assets[1].data.len).to.equal(200); + expect(nativeRequest.assets[2].required).to.equal(0); + expect(nativeRequest.assets[3].img).to.not.equal(null); + expect(nativeRequest.assets[3].img.wmin).to.equal(50); + expect(nativeRequest.assets[3].img.hmin).to.equal(50); + expect(nativeRequest.assets[3].img.type).to.equal(1); + expect(nativeRequest.assets[4].img).to.not.equal(null); + expect(nativeRequest.assets[4].img.wmin).to.equal(100); + expect(nativeRequest.assets[4].img.hmin).to.equal(150); + expect(nativeRequest.assets[4].img.type).to.equal(3); + }); + + it('Verify Native response', () => { + const request = spec.buildRequests(nativeSlotConfig); + expect(request.url).to.equal('//piohbdisp.hb.adx1.com/'); + expect(request.method).to.equal('POST'); + const ortbRequest = JSON.parse(request.data); + const nativeResponse = { + 'native': { + assets: [ + { id: 1, title: { text: 'Ad Title' } }, + { id: 2, data: { value: 'Test description' } }, + { id: 3, data: { value: 'Brand' } }, + { id: 4, img: { url: 'https://s3.amazonaws.com/adx1public/creatives_icon.png' } }, + { id: 5, img: { url: 'https://s3.amazonaws.com/adx1public/creatives_image.png' } } + ], + link: { url: 'http://brand.com/' } + } + }; + const ortbResponse = { + seatbid: [{ + bid: [{ + impid: ortbRequest.imp[0].id, + price: 1.25, + nurl: 'http://rtb.adx1.com/log', + adm: JSON.stringify(nativeResponse) + }] + }], + cur: 'USD', + }; + const bids = spec.interpretResponse({ body: ortbResponse }, request); + // verify bid + const bid = bids[0]; + expect(bid.cpm).to.equal(1.25); + expect(bid.adId).to.equal('bid12345'); + expect(bid.ad).to.be.undefined; + expect(bid.mediaType).to.equal('native'); + const nativeBid = bid['native']; + expect(nativeBid).to.not.equal(null); + expect(nativeBid.title).to.equal('Ad Title'); + expect(nativeBid.sponsoredBy).to.equal('Brand'); + expect(nativeBid.icon).to.equal('https://s3.amazonaws.com/adx1public/creatives_icon.png'); + expect(nativeBid.image).to.equal('https://s3.amazonaws.com/adx1public/creatives_image.png'); + expect(nativeBid.clickUrl).to.equal(encodeURIComponent('http://brand.com/')); + expect(nativeBid.impressionTrackers).to.have.lengthOf(1); + expect(nativeBid.impressionTrackers[0]).to.equal('http://rtb.adx1.com/log'); + }); + it('Verifies bidder code', () => { expect(spec.code).to.equal('platformio'); }); + it('Verifies supported media types', () => { + expect(spec.supportedMediaTypes).to.have.lengthOf(1); + expect(spec.supportedMediaTypes[0]).to.equal('native'); + }); + it('Verifies if bid request valid', () => { expect(spec.isBidRequestValid(slotConfigs[0])).to.equal(true); - expect(spec.isBidRequestValid({})).to.equal(false); - expect(spec.isBidRequestValid({ params: {} })).to.equal(false); + expect(spec.isBidRequestValid(slotConfigs[1])).to.equal(true); }); }); diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index 608ac102ace..58a71158865 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -243,6 +243,41 @@ describe('S2S Adapter', () => { expect(requestBid.ad_units[0].bids[0].params.placementId).to.exist.and.to.be.a('number'); expect(requestBid.ad_units[0].bids[0].params.member).to.exist.and.to.be.a('string'); }); + + it('adds digitrust id is present and user is not optout', () => { + let digiTrustObj = { + success: true, + identity: { + privacy: { + optout: false + }, + id: 'testId', + keyv: 'testKeyV' + } + }; + + window.DigiTrust = { + getUser: () => digiTrustObj + }; + + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + let requestBid = JSON.parse(requests[0].requestBody); + + expect(requestBid.digiTrust).to.deep.equal({ + id: digiTrustObj.identity.id, + keyv: digiTrustObj.identity.keyv, + pref: 0 + }); + + digiTrustObj.identity.privacy.optout = true; + + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + requestBid = JSON.parse(requests[1].requestBody); + + expect(requestBid.digiTrust).to.not.exist; + + delete window.DigiTrust; + }); }); describe('response handler', () => { diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index b02f01ecd93..2ff8d5fe0e3 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -760,6 +760,8 @@ describe('the rubicon adapter', () => { 'https://fastlane-adv.rubiconproject.com/v1/creative/a40fe16e-d08d-46a9-869d-2e1573599e0c.xml' ); expect(bids[0].impression_id).to.equal('a40fe16e-d08d-46a9-869d-2e1573599e0c'); + expect(bids[0].mediaType).to.equal('video'); + expect(bids[0].videoCacheKey).to.equal('a40fe16e-d08d-46a9-869d-2e1573599e0c'); }); }); }); diff --git a/test/spec/modules/serverbidBidAdapter_spec.js b/test/spec/modules/serverbidBidAdapter_spec.js index fb31f925c6e..d3dc64ae6df 100644 --- a/test/spec/modules/serverbidBidAdapter_spec.js +++ b/test/spec/modules/serverbidBidAdapter_spec.js @@ -245,10 +245,10 @@ describe('Serverbid BidAdapter', () => { expect(opts).to.be.empty; }); - it('should always return empty array', () => { + it('should return a sync url if iframe syncs are enabled', () => { let opts = spec.getUserSyncs(syncOptions); - expect(opts).to.be.empty; + expect(opts.length).to.equal(1); }); }); }); diff --git a/test/spec/modules/sonobiBidAdapter_spec.js b/test/spec/modules/sonobiBidAdapter_spec.js new file mode 100644 index 00000000000..6a44c817d0b --- /dev/null +++ b/test/spec/modules/sonobiBidAdapter_spec.js @@ -0,0 +1,243 @@ +import { expect } from 'chai' +import { spec } from 'modules/sonobiBidAdapter' +import { newBidder } from 'src/adapters/bidderFactory' + +describe('SonobiBidAdapter', () => { + const adapter = newBidder(spec) + + describe('.code', () => { + it('should return a bidder code of sonobi', () => { + expect(spec.code).to.equal('sonobi') + }) + }) + + describe('inherited functions', () => { + it('should exist and be a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function') + }) + }) + + describe('.isBidRequestValid', () => { + let bid = { + 'bidder': 'sonobi', + 'params': { + 'ad_unit': '/7780971/sparks_prebid_MR', + 'sizes': [[300, 250], [300, 600]], + 'floor': '1' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + } + + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(bid)).to.equal(true) + }) + + it('should return true when bid.params.placement_id and bid.params.sizes are found', () => { + let bid = Object.assign({}, bid) + delete bid.params + delete bid.sizes + bid.params = { + 'placement_id': '1a2b3c4d5e6f1a2b3c4d', + 'sizes': [[300, 250], [300, 600]], + } + + expect(spec.isBidRequestValid(bid)).to.equal(true) + }) + + it('should return true when bid.params.placement_id and bid.sizes are found', () => { + let bid = Object.assign({}, bid) + delete bid.params + bid.sizes = [[300, 250], [300, 600]] + bid.params = { + 'placement_id': '1a2b3c4d5e6f1a2b3c4d', + } + + expect(spec.isBidRequestValid(bid)).to.equal(true) + }) + + it('should return true when bid.params.ad_unit and bid.params.sizes are found', () => { + let bid = Object.assign({}, bid) + delete bid.params + delete bid.sizes + bid.params = { + 'ad_unit': '/7780971/sparks_prebid_MR', + 'sizes': [[300, 250], [300, 600]], + } + + expect(spec.isBidRequestValid(bid)).to.equal(true) + }) + + it('should return true when bid.params.ad_unit and bid.sizes are found', () => { + let bid = Object.assign({}, bid) + delete bid.params + bid.sizes = [[300, 250], [300, 600]] + bid.params = { + 'ad_unit': '/7780971/sparks_prebid_MR', + } + + expect(spec.isBidRequestValid(bid)).to.equal(true) + }) + + it('should return false when no params are found', () => { + let bid = Object.assign({}, bid) + delete bid.params + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) + + it('should return false when bid.params.placement_id and bid.params.ad_unit are not found', () => { + let bid = Object.assign({}, bid) + delete bid.params + bid.params = { + 'placement_id': 0, + 'ad_unit': 0, + 'sizes': [[300, 250], [300, 600]], + } + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) + }) + + describe('.buildRequests', () => { + let bidRequest = [{ + 'bidder': 'sonobi', + 'params': { + 'placement_id': '1a2b3c4d5e6f1a2b3c4d', + 'sizes': [[300, 250], [300, 600]], + 'floor': '1.25', + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1f', + }, + { + 'bidder': 'sonobi', + 'params': { + 'ad_unit': '/7780971/sparks_prebid_LB', + 'sizes': [[300, 250], [300, 600]], + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[120, 600], [300, 600], [160, 600]], + 'bidId': '30b31c1838de1e', + }]; + + let keyMakerData = { + '30b31c1838de1f': '1a2b3c4d5e6f1a2b3c4d|300x250,300x600|f=1.25', + '/7780971/sparks_prebid_LB|30b31c1838de1e': '300x250,300x600', + }; + + it('should return a properly formatted request', () => { + const bidRequests = spec.buildRequests(bidRequest) + const bidRequestsPageViewID = spec.buildRequests(bidRequest) + expect(bidRequests.url).to.equal('https://apex.go.sonobi.com/trinity.json') + expect(bidRequests.method).to.equal('GET') + expect(bidRequests.data.key_maker).to.deep.equal(JSON.stringify(keyMakerData)) + expect(bidRequests.data.ref).not.to.be.empty + expect(bidRequests.data.s).not.to.be.empty + expect(bidRequests.data.pv).to.equal(bidRequestsPageViewID.data.pv) + expect(bidRequests.data.hfa).to.not.exist + }) + + it('should return a properly formatted request with hfa', () => { + bidRequest[0].params.hfa = 'hfakey' + bidRequest[1].params.hfa = 'hfakey' + const bidRequests = spec.buildRequests(bidRequest) + expect(bidRequests.url).to.equal('https://apex.go.sonobi.com/trinity.json') + expect(bidRequests.method).to.equal('GET') + expect(bidRequests.data.ref).not.to.be.empty + expect(bidRequests.data.s).not.to.be.empty + expect(bidRequests.data.hfa).to.equal('hfakey') + }) + }) + + describe('.interpretResponse', () => { + let bidResponse = { + 'body': { + 'slots': { + '/7780971/sparks_prebid_LB|30b31c1838de1d': { + 'sbi_size': '300x600', + 'sbi_apoc': 'remnant', + 'sbi_aid': '30292e432662bd5f86d90774b944b039', + 'sbi_mouse': 1.07, + }, + '30b31c1838de1f': { + 'sbi_size': '300x250', + 'sbi_apoc': 'remnant', + 'sbi_aid': '30292e432662bd5f86d90774b944b038', + 'sbi_mouse': 1.25, + 'sbi_dozer': 'dozerkey', + }, + '30b31c1838de1e': {}, + }, + 'sbi_dc': 'mco-1-', + 'sbi_px': [{ + 'code': 'so', + 'delay': 0, + 'url': 'https://example.com/pixel.png', + 'type': 'image' + }], + 'sbi_suid': 'af99f47a-e7b1-4791-ab32-34952d87c5a0', + } + }; + + let prebidResponse = [{ + 'requestId': '30b31c1838de1d', + 'cpm': 1.07, + 'width': 300, + 'height': 600, + 'ad': '', + 'ttl': 500, + 'creativeId': '30292e432662bd5f86d90774b944b039', + 'netRevenue': true, + 'currency': 'USD' + }, { + 'requestId': '30b31c1838de1f', + 'cpm': 1.25, + 'width': 300, + 'height': 250, + 'ad': '', + 'ttl': 500, + 'creativeId': '30292e432662bd5f86d90774b944b038', + 'netRevenue': true, + 'currency': 'USD', + 'dealId': 'dozerkey' + }]; + + it('should map bidResponse to prebidResponse', () => { + const response = spec.interpretResponse(bidResponse); + expect(response).to.deep.equal(prebidResponse); + }) + }) + + describe('.getUserSyncs', () => { + let bidResponse = [{ + 'body': { + 'sbi_px': [{ + 'code': 'so', + 'delay': 0, + 'url': 'https://pixel-test', + 'type': 'image' + }] + } + }]; + + it('should return one sync pixel', () => { + expect(spec.getUserSyncs({ pixelEnabled: true }, bidResponse)).to.deep.equal([{ + type: 'image', + url: 'https://pixel-test' + }]); + }) + + it('should return an empty array when sync is enabled but no sync pixel returned', () => { + const pixel = Object.assign({}, bidResponse); + delete pixel[0].body.sbi_px; + expect(spec.getUserSyncs({ pixelEnabled: true }, bidResponse)).to.have.length(0); + }) + + it('should return an empty array', () => { + expect(spec.getUserSyncs({ pixelEnabled: false }, bidResponse)).to.have.length(0); + }) + }) +}) diff --git a/test/spec/modules/sovrnBidAdapter_spec.js b/test/spec/modules/sovrnBidAdapter_spec.js index c4eadd02738..43307f35527 100644 --- a/test/spec/modules/sovrnBidAdapter_spec.js +++ b/test/spec/modules/sovrnBidAdapter_spec.js @@ -103,7 +103,7 @@ describe('sovrnBidAdapter', function() { }); it('should get correct bid response when dealId is passed', () => { - response.body.dealId = 'baking'; + response.body.dealid = 'baking'; let expectedResponse = [{ 'requestId': '263c448586f5a1', diff --git a/test/spec/modules/ucfunnelBidAdapter_spec.js b/test/spec/modules/ucfunnelBidAdapter_spec.js new file mode 100644 index 00000000000..152c7c39b1e --- /dev/null +++ b/test/spec/modules/ucfunnelBidAdapter_spec.js @@ -0,0 +1,108 @@ +import { expect } from 'chai'; +import { spec } from 'modules/ucfunnelBidAdapter'; + +const URL = '//hb.aralego.com/header'; +const BIDDER_CODE = 'ucfunnel'; +const validBidReq = { + bidder: BIDDER_CODE, + params: { + adid: 'test-ad-83444226E44368D1E32E49EEBE6D29' + }, + sizes: [[300, 250]], + bidId: '263be71e91dd9d', + auctionId: '9ad1fa8d-2297-4660-a018-b39945054746', +}; + +const invalidBidReq = { + bidder: BIDDER_CODE, + params: { + adid: 123456789 + }, + sizes: [[300, 250]], + bidId: '263be71e91dd9d', + auctionId: '9ad1fa8d-2297-4660-a018-b39945054746' +}; + +const bidReq = [{ + bidder: BIDDER_CODE, + params: { + adid: 'test-ad-83444226E44368D1E32E49EEBE6D29' + }, + sizes: [[300, 250]], + bidId: '263be71e91dd9d', + auctionId: '9ad1fa8d-2297-4660-a018-b39945054746' +}]; + +const validBidRes = { + ad_id: 'ad-83444226E44368D1E32E49EEBE6D29', + adm: '
', + cpm: 0.01, + height: 250, + width: 300 +}; + +const bidResponse = validBidRes; + +const bidResArray = [ + validBidRes, + { + ad: '', + bidRequestId: '263be71e91dd9d', + cpm: 100, + adId: '123abc', + currency: 'USD', + netRevenue: true, + width: 300, + height: 250, + ttl: 360 + }, + { + ad: '
Hello
', + bidRequestId: '', + cpm: 0, + adId: '123abc', + currency: 'USD', + netRevenue: true, + width: 300, + height: 250, + ttl: 360 + } +]; + +describe('ucfunnel Adapter', () => { + describe('request', () => { + it('should validate bid request', () => { + expect(spec.isBidRequestValid(validBidReq)).to.equal(true); + }); + it('should not validate incorrect bid request', () => { + expect(spec.isBidRequestValid(invalidBidReq)).to.equal(false); + }); + }); + describe('build request', () => { + it('Verify bid request', () => { + const request = spec.buildRequests(bidReq); + expect(request[0].method).to.equal('GET'); + expect(request[0].url).to.equal(URL); + expect(request[0].data).to.match(new RegExp(`${bidReq[0].params.adid}`)); + }); + }); + + describe('interpretResponse', () => { + it('should build bid array', () => { + const request = spec.buildRequests(bidReq); + const result = spec.interpretResponse({body: bidResponse}, request[0]); + expect(result.length).to.equal(1); + }); + + it('should have all relevant fields', () => { + const request = spec.buildRequests(bidReq); + const result = spec.interpretResponse({body: bidResponse}, request[0]); + const bid = result[0]; + + expect(bid.requestId).to.equal('263be71e91dd9d'); + expect(bid.cpm).to.equal(0.01); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + }); + }); +}); diff --git a/test/spec/modules/vidazooBidAdapter_spec.js b/test/spec/modules/vidazooBidAdapter_spec.js new file mode 100644 index 00000000000..a4f8d0dfdb2 --- /dev/null +++ b/test/spec/modules/vidazooBidAdapter_spec.js @@ -0,0 +1,183 @@ +import {expect} from 'chai'; +import {spec as adapter, URL} from 'modules/vidazooBidAdapter'; +import * as utils from 'src/utils'; +const BID = { + 'bidId': '2d52001cabd527', + 'params': { + 'cId': '59db6b3b4ffaa70004f45cdc', + 'pId': '59ac17c192832d0011283fe3', + 'bidFloor': 0.1 + }, + 'placementCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', + 'sizes': [[300, 250], [300, 600]], + 'bidderRequestId': '1fdb5ff1b6eaa7', + 'requestId': 'b0777d85-d061-450e-9bc7-260dd54bbb7a' +}; + +const SERVER_RESPONSE = { + body: { + 'ad': '', + 'price': 0.8, + 'creativeId': '12610997325162499419', + 'exp': 30, + 'cookies': [{ + 'src': 'https://sync.com', + 'type': 'iframe' + }, { + 'src': 'https://sync.com', + 'type': 'img' + }] + } +}; + +const REQUEST = { + data: { + width: 300, + height: 250, + bidId: '2d52001cabd527' + } +}; + +const SYNC_OPTIONS = { + 'pixelEnabled': true +}; + +describe('VidazooBidAdapter', () => { + describe('validtae spec', () => { + it('exists and is a function', () => { + expect(adapter.isBidRequestValid).to.exist.and.to.be.a('function'); + }); + + it('exists and is a function', () => { + expect(adapter.buildRequests).to.exist.and.to.be.a('function'); + }); + + it('exists and is a function', () => { + expect(adapter.interpretResponse).to.exist.and.to.be.a('function'); + }); + + it('exists and is a function', () => { + expect(adapter.getUserSyncs).to.exist.and.to.be.a('function'); + }); + + it('exists and is a string', () => { + expect(adapter.code).to.exist.and.to.be.a('string'); + }); + }); + + describe('validate bid requests', () => { + it('should require cId', () => { + const isValid = adapter.isBidRequestValid({ + params: { + pId: 'pid' + } + }); + expect(isValid).to.be.false; + }); + + it('should require pId', () => { + const isValid = adapter.isBidRequestValid({ + params: { + cId: 'cid' + } + }); + expect(isValid).to.be.false; + }); + + it('should validate correctly', () => { + const isValid = adapter.isBidRequestValid({ + params: { + cId: 'cid', + pId: 'pid' + } + }); + expect(isValid).to.be.true; + }); + }); + + describe('build requests', () => { + let sandbox; + before(() => { + sandbox = sinon.sandbox.create(); + sandbox.stub(utils, 'getTopWindowUrl', () => 'http://www.greatsite.com'); + sandbox.stub(Date, 'now', () => 1000); + }); + + it('should build request for each size', () => { + const requests = adapter.buildRequests([BID]); + expect(requests).to.have.length(2); + expect(requests[0]).to.deep.equal({ + method: 'GET', + url: `${URL}/prebid/59db6b3b4ffaa70004f45cdc`, + data: { + width: '300', + height: '250', + url: 'http://www.greatsite.com', + cb: 1000, + bidFloor: 0.1, + bidId: '2d52001cabd527', + publisherId: '59ac17c192832d0011283fe3' + } + }); + expect(requests[1]).to.deep.equal({ + method: 'GET', + url: `${URL}/prebid/59db6b3b4ffaa70004f45cdc`, + data: { + width: '300', + height: '600', + url: 'http://www.greatsite.com', + cb: 1000, + bidFloor: 0.1, + bidId: '2d52001cabd527', + publisherId: '59ac17c192832d0011283fe3' + } + }); + }); + + after(() => { + sandbox.restore(); + }); + }); + + describe('interpret response', () => { + it('should return empty array when there is no response', () => { + const responses = adapter.interpretResponse(null); + expect(responses).to.be.empty; + }); + + it('should return empty array when there is no ad', () => { + const responses = adapter.interpretResponse({price: 1, ad: ''}); + expect(responses).to.be.empty; + }); + + it('should return empty array when there is no price', () => { + const responses = adapter.interpretResponse({price: null, ad: 'great ad'}); + expect(responses).to.be.empty; + }); + + it('should return an array of interpreted responses', () => { + const responses = adapter.interpretResponse(SERVER_RESPONSE, REQUEST); + expect(responses).to.have.length(1); + expect(responses[0]).to.deep.equal({ + requestId: '2d52001cabd527', + cpm: 0.8, + width: 300, + height: 250, + creativeId: '12610997325162499419', + currency: 'USD', + netRevenue: true, + ttl: 30, + ad: '' + }); + }); + + it('should take default TTL', () => { + const serverResponse = utils.deepClone(SERVER_RESPONSE); + delete serverResponse.body.exp; + const responses = adapter.interpretResponse(serverResponse, REQUEST); + expect(responses).to.have.length(1); + expect(responses[0].ttl).to.equal(300); + }); + }); +}); diff --git a/test/spec/modules/xendizBidAdapter_spec.js b/test/spec/modules/xendizBidAdapter_spec.js new file mode 100644 index 00000000000..66b9dc62b88 --- /dev/null +++ b/test/spec/modules/xendizBidAdapter_spec.js @@ -0,0 +1,119 @@ +import { expect } from 'chai'; +import { spec } from 'modules/xendizBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; + +const VALID_ENDPOINT = '//prebid.xendiz.com/request'; +const bidRequest = { + bidder: 'xendiz', + adUnitCode: 'test-div', + sizes: [[300, 250], [300, 600]], + params: { + pid: '550e8400-e29b-41d4-a716-446655440000' + }, + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', +}; + +const bidResponse = { + body: { + id: '1d1a030790a475', + bids: [{ + id: '30b31c1838de1e', + price: 3, + cur: 'USD', + h: 250, + w: 300, + crid: 'test', + dealid: '1', + exp: 900, + adm: 'tag' + }] + } +}; + +const noBidResponse = { body: { id: '1d1a030790a475', bids: [] } }; + +describe('xendizBidAdapter', () => { + const adapter = newBidder(spec); + + describe('inherited functions', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', () => { + it('should return false', () => { + let bid = Object.assign({}, bidRequest); + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return true', () => { + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + }); + + describe('buildRequests', () => { + it('should format valid url', () => { + const request = spec.buildRequests([bidRequest]); + expect(request.url).to.equal(VALID_ENDPOINT); + }); + + it('should format valid url', () => { + const request = spec.buildRequests([bidRequest]); + expect(request.url).to.equal(VALID_ENDPOINT); + }); + + it('should format valid request body', () => { + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + expect(payload.id).to.exist; + expect(payload.items).to.exist; + expect(payload.device).to.exist; + }); + + it('should attach valid device info', () => { + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + expect(payload.device).to.deep.equal([ + navigator.language || '', + window.screen.width, + window.screen.height + ]); + }); + + it('should transform sizes', () => { + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + const item = payload.items[0]; + expect(item[item.length - 1]).to.deep.equal(['300x250', '300x600']); + }); + }); + + describe('interpretResponse', () => { + it('should get correct bid response', () => { + const result = spec.interpretResponse(bidResponse); + const validResponse = [{ + requestId: '30b31c1838de1e', + cpm: 3, + width: 300, + height: 250, + creativeId: 'test', + netRevenue: true, + dealId: '1', + currency: 'USD', + ttl: 900, + ad: 'tag' + }]; + + expect(result).to.deep.equal(validResponse); + }); + + it('handles nobid responses', () => { + let result = spec.interpretResponse(noBidResponse); + expect(result.length).to.equal(0); + }); + }); +}); diff --git a/test/spec/modules/yieldlabBidAdapter_spec.js b/test/spec/modules/yieldlabBidAdapter_spec.js new file mode 100644 index 00000000000..2a19763b10a --- /dev/null +++ b/test/spec/modules/yieldlabBidAdapter_spec.js @@ -0,0 +1,111 @@ +import { expect } from 'chai' +import { spec } from 'modules/yieldlabBidAdapter' +import { newBidder } from 'src/adapters/bidderFactory' + +const REQUEST = { + 'bidder': 'yieldlab', + 'params': { + 'placementId': '1111', + 'accountId': '2222', + 'adSize': '800x250' + }, + 'bidderRequestId': '143346cf0f1731', + 'auctionId': '2e41f65424c87c', + 'adUnitCode': 'adunit-code', + 'bidId': '2d925f27f5079f', + 'sizes': [1, 1] +} + +const RESPONSE = { + advertiser: 'yieldlab', + curl: 'https://www.yieldlab.de', + format: 0, + id: 1111, + price: 1 +} + +describe('yieldlabBidAdapter', () => { + const adapter = newBidder(spec) + + describe('inherited functions', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function') + }) + }) + + describe('isBidRequestValid', () => { + it('should return true when required params found', () => { + const request = { + 'params': { + 'placementId': '1111', + 'accountId': '2222', + 'adSize': '800x250' + } + } + expect(spec.isBidRequestValid(request)).to.equal(true) + }) + + it('should return false when required params are not passed', () => { + expect(spec.isBidRequestValid({})).to.equal(false) + }) + }) + + describe('buildRequests', () => { + const bidRequests = [REQUEST] + const request = spec.buildRequests(bidRequests) + + it('sends bid request to ENDPOINT via GET', () => { + expect(request.method).to.equal('GET') + }) + + it('returns a list of valid requests', () => { + expect(request.validBidRequests).to.eql([REQUEST]) + }) + }) + + describe('interpretResponse', () => { + const validRequests = { + validBidRequests: [REQUEST] + } + + it('handles nobid responses', () => { + expect(spec.interpretResponse({body: {}}, {validBidRequests: []}).length).to.equal(0) + expect(spec.interpretResponse({body: []}, {validBidRequests: []}).length).to.equal(0) + }) + + it('should get correct bid response', () => { + const result = spec.interpretResponse({body: [RESPONSE]}, validRequests) + + expect(result[0].requestId).to.equal('2d925f27f5079f') + expect(result[0].cpm).to.equal(0.01) + expect(result[0].width).to.equal(800) + expect(result[0].height).to.equal(250) + expect(result[0].creativeId).to.equal('1111') + expect(result[0].dealId).to.equal(undefined) + expect(result[0].currency).to.equal('EUR') + expect(result[0].netRevenue).to.equal(true) + expect(result[0].ttl).to.equal(600) + expect(result[0].referrer).to.equal('') + expect(result[0].ad).to.include('
', + creativeId: '9874652394875' + }], + header: 'header?' + }; + }) + + it('should correctly reorder the server response', () => { + const newResponse = spec.interpretResponse(serverResponse); + expect(newResponse.length).to.be.equal(1); + expect(newResponse[0]).to.deep.equal({ + requestId: '21989fdbef550a', + cpm: 3.45455, + width: 300, + height: 250, + creativeId: '9874652394875', + currency: 'USD', + netRevenue: true, + ttl: 300, + ad: '
' + }); + }); + + it('should not add responses if the cpm is 0 or null', () => { + serverResponse.body[0].cpm = 0; + let response = spec.interpretResponse(serverResponse); + expect(response).to.deep.equal([]); + + serverResponse.body[0].cpm = null; + response = spec.interpretResponse(serverResponse); + expect(response).to.deep.equal([]) + }); + }); + + describe('getUserSync', () => { + const SYNC_ENDPOINT = 'https://static.yieldmo.com/blank.min.html?orig='; + let options = { + iframeEnabled: true, + pixelEnabled: true + }; + + it('should return a tracker with type and url as parameters', () => { + if (/iPhone|iPad|iPod/i.test(window.navigator.userAgent)) { + expect(spec.getUserSync(options)).to.deep.equal([{ + type: 'iframe', + url: SYNC_ENDPOINT + utils.getOrigin() + }]); + + options.iframeEnabled = false; + expect(spec.getUserSync(options)).to.deep.equal([]); + } else { + // not ios, so tracker will fail + expect(spec.getUserSync(options)).to.deep.equal([]); + } + }); + }); +}); diff --git a/test/spec/utils_spec.js b/test/spec/utils_spec.js index 9f7c57ccb81..96d3888015e 100755 --- a/test/spec/utils_spec.js +++ b/test/spec/utils_spec.js @@ -525,94 +525,89 @@ describe('Utils', function () { }); }); - /** - * tests fail in IE10 because __lookupSetter__ and __lookupGetter__ are - * not supported. See #1656. commenting out until they can be fixed. - * - * describe('cookie support', function () { - * // store original cookie getter and setter so we can reset later - * var origCookieSetter = document.__lookupSetter__('cookie'); - * var origCookieGetter = document.__lookupGetter__('cookie'); - * - * // store original cookieEnabled getter and setter so we can reset later - * var origCookieEnabledSetter = window.navigator.__lookupSetter__('cookieEnabled'); - * var origCookieEnabledGetter = window.navigator.__lookupGetter__('cookieEnabled'); - * - * // Replace the document cookie set function with the output of a custom function for testing - * let setCookie = (v) => v; - * - * beforeEach(() => { - * // Redefine window.navigator.cookieEnabled such that you can set otherwise "read-only" values - * Object.defineProperty(window.navigator, 'cookieEnabled', (function (_value) { - * return { - * get: function _get() { - * return _value; - * }, - * set: function _set(v) { - * _value = v; - * }, - * configurable: true - * }; - * })(window.navigator.cookieEnabled)); - * - * // Reset the setCookie cookie function before each test - * setCookie = (v) => v; - * // Redefine the document.cookie object such that you can purposefully have it output nothing as if it is disabled - * Object.defineProperty(window.document, 'cookie', (function (_value) { - * return { - * get: function _get() { - * return _value; - * }, - * set: function _set(v) { - * _value = setCookie(v); - * }, - * configurable: true - * }; - * })(window.navigator.cookieEnabled)); - * }); - * - * afterEach(() => { - * // redefine window.navigator.cookieEnabled to original getter and setter - * Object.defineProperty(window.navigator, 'cookieEnabled', { - * get: origCookieEnabledGetter, - * set: origCookieEnabledSetter, - * configurable: true - * }); - * // redefine document.cookie to original getter and setter - * Object.defineProperty(document, 'cookie', { - * get: origCookieGetter, - * set: origCookieSetter, - * configurable: true - * }); - * }); - * - * it('should be detected', function() { - * assert.equal(utils.cookiesAreEnabled(), true, 'Cookies should be enabled by default'); - * }); - * - * it('should be not available', function() { - * setCookie = () => ''; - * window.navigator.cookieEnabled = false; - * window.document.cookie = ''; - * assert.equal(utils.cookiesAreEnabled(), false, 'Cookies should be disabled'); - * }); - * - * it('should be available', function() { - * window.navigator.cookieEnabled = false; - * window.document.cookie = 'key=value'; - * assert.equal(utils.cookiesAreEnabled(), true, 'Cookies should already be set'); - * window.navigator.cookieEnabled = false; - * window.document.cookie = ''; - * assert.equal(utils.cookiesAreEnabled(), true, 'Cookies should settable'); - * setCookie = () => ''; - * window.navigator.cookieEnabled = true; - * window.document.cookie = ''; - * assert.equal(utils.cookiesAreEnabled(), true, 'Cookies should be on via on window.navigator'); - * // Reset the setCookie - * setCookie = (v) => v; - * }); - * }); - **/ + // describe('cookie support', function () { + // // store original cookie getter and setter so we can reset later + // var origCookieSetter = document.__lookupSetter__('cookie'); + // var origCookieGetter = document.__lookupGetter__('cookie'); + + // // store original cookieEnabled getter and setter so we can reset later + // var origCookieEnabledSetter = window.navigator.__lookupSetter__('cookieEnabled'); + // var origCookieEnabledGetter = window.navigator.__lookupGetter__('cookieEnabled'); + + // // Replace the document cookie set function with the output of a custom function for testing + // let setCookie = (v) => v; + + // beforeEach(() => { + // // Redefine window.navigator.cookieEnabled such that you can set otherwise "read-only" values + // Object.defineProperty(window.navigator, 'cookieEnabled', (function (_value) { + // return { + // get: function _get() { + // return _value; + // }, + // set: function _set(v) { + // _value = v; + // }, + // configurable: true + // }; + // })(window.navigator.cookieEnabled)); + + // // Reset the setCookie cookie function before each test + // setCookie = (v) => v; + // // Redefine the document.cookie object such that you can purposefully have it output nothing as if it is disabled + // Object.defineProperty(window.document, 'cookie', (function (_value) { + // return { + // get: function _get() { + // return _value; + // }, + // set: function _set(v) { + // _value = setCookie(v); + // }, + // configurable: true + // }; + // })(window.navigator.cookieEnabled)); + // }); + + // afterEach(() => { + // // redefine window.navigator.cookieEnabled to original getter and setter + // Object.defineProperty(window.navigator, 'cookieEnabled', { + // get: origCookieEnabledGetter, + // set: origCookieEnabledSetter, + // configurable: true + // }); + // // redefine document.cookie to original getter and setter + // Object.defineProperty(document, 'cookie', { + // get: origCookieGetter, + // set: origCookieSetter, + // configurable: true + // }); + // }); + + // it('should be detected', function() { + // assert.equal(utils.cookiesAreEnabled(), true, 'Cookies should be enabled by default'); + // }); + + // it('should be not available', function() { + // setCookie = () => ''; + // window.navigator.cookieEnabled = false; + // window.document.cookie = ''; + // assert.equal(utils.cookiesAreEnabled(), false, 'Cookies should be disabled'); + // }); + + // it('should be available', function() { + // window.navigator.cookieEnabled = false; + // window.document.cookie = 'key=value'; + // assert.equal(utils.cookiesAreEnabled(), true, 'Cookies should already be set'); + // window.navigator.cookieEnabled = false; + // window.document.cookie = ''; + // assert.equal(utils.cookiesAreEnabled(), true, 'Cookies should settable'); + // setCookie = () => ''; + // window.navigator.cookieEnabled = true; + // window.document.cookie = ''; + // assert.equal(utils.cookiesAreEnabled(), true, 'Cookies should be on via on window.navigator'); + // // Reset the setCookie + // setCookie = (v) => v; + // }); + // }); describe('delayExecution', function () { it('should execute the core function after the correct number of calls', function () {