diff --git a/.circleci/config.yml b/.circleci/config.yml index fbf7e77e10f..16bdd5b317e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -47,4 +47,4 @@ jobs: # run tests! - run: name: BrowserStack testing - command: gulp test --browserstack + command: gulp test --browserstack --nolintfix diff --git a/gulpfile.js b/gulpfile.js index 8775b651b9f..d3b29aa66a7 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -83,7 +83,7 @@ function lint(done) { return file.eslint != null && file.eslint.fixed; } return gulp.src(['src/**/*.js', 'modules/**/*.js', 'test/**/*.js'], {base: './'}) - .pipe(eslint({fix: true})) + .pipe(gulpif(argv.nolintfix, eslint(), eslint({fix: true}))) .pipe(eslint.format('stylish')) .pipe(eslint.failAfterError()) .pipe(gulpif(isFixed, gulp.dest('./'))); diff --git a/modules/adgenerationBidAdapter.js b/modules/adgenerationBidAdapter.js index 1e00485a6d9..77b6acbf0e0 100644 --- a/modules/adgenerationBidAdapter.js +++ b/modules/adgenerationBidAdapter.js @@ -1,7 +1,7 @@ import * as utils from '../src/utils'; import {registerBidder} from '../src/adapters/bidderFactory'; import {BANNER, NATIVE} from '../src/mediaTypes'; -import { config } from '../src/config'; +import {config} from '../src/config'; const ADG_BIDDER_CODE = 'adgeneration'; export const spec = { @@ -156,6 +156,9 @@ function createNativeAd(body) { case 6: native.cta = assets[i].data.value; break; + case 502: + native.privacyLink = encodeURIComponent(assets[i].data.value); + break; } } native.clickUrl = body.native_ad.link.url; diff --git a/modules/adgenerationBidAdapter.md b/modules/adgenerationBidAdapter.md index 7d8281be9b2..7dfc301e657 100644 --- a/modules/adgenerationBidAdapter.md +++ b/modules/adgenerationBidAdapter.md @@ -52,7 +52,10 @@ var adUnits = [ }, icon: { required: true - } + }, + privacyLink: { + required: true + }, }, }, bids: [ diff --git a/modules/adpod.js b/modules/adpod.js index 87e5869e17b..fe49c10e193 100644 --- a/modules/adpod.js +++ b/modules/adpod.js @@ -132,6 +132,7 @@ function attachPriceIndustryDurationKeyToBid(bid, brandCategoryExclusion) { } bid.adserverTargeting[TARGETING_KEY_PB_CAT_DUR] = pcd; bid.adserverTargeting[TARGETING_KEY_CACHE_ID] = initialCacheKey; + bid.videoCacheKey = initialCacheKey; bid.customCacheKey = `${pcd}_${initialCacheKey}`; } @@ -412,3 +413,18 @@ export function callPrebidCacheAfterAuction(bids, callback) { } }) } + +/** + * Compare function to be used in sorting long-form bids. This will compare bids on price per second. + * @param {Object} bid + * @param {Object} bid + */ +export function sortByPricePerSecond(a, b) { + if (a.cpm / a.video.durationBucket < b.cpm / b.video.durationBucket) { + return 1; + } + if (a.cpm / a.video.durationBucket > b.cpm / b.video.durationBucket) { + return -1; + } + return 0; +} diff --git a/modules/advangelistsBidAdapter.js b/modules/advangelistsBidAdapter.js old mode 100644 new mode 100755 index 926be211649..e98de8dd77e --- a/modules/advangelistsBidAdapter.js +++ b/modules/advangelistsBidAdapter.js @@ -7,7 +7,7 @@ import find from 'core-js/library/fn/array/find'; import includes from 'core-js/library/fn/array/includes'; const ADAPTER_VERSION = '1.0'; -const BIDDER_CODE = 'avng'; +const BIDDER_CODE = 'advangelists'; export const VIDEO_ENDPOINT = '//nep.advangelists.com/xp/get?pubid=';// 0cf8d6d643e13d86a5b6374148a4afac'; export const BANNER_ENDPOINT = '//nep.advangelists.com/xp/get?pubid=';// 0cf8d6d643e13d86a5b6374148a4afac'; diff --git a/modules/advangelistsBidAdapter.md b/modules/advangelistsBidAdapter.md old mode 100644 new mode 100755 index 14e2befd48f..1765241eaf3 --- a/modules/advangelistsBidAdapter.md +++ b/modules/advangelistsBidAdapter.md @@ -24,7 +24,7 @@ var displayAdUnit = [ [320, 50] ], bids: [{ - bidder: 'avng', + bidder: 'advangelists', params: { pubid: '0cf8d6d643e13d86a5b6374148a4afac', placement: 1234 @@ -47,7 +47,7 @@ var videoAdUnit = { }, bids: [ { - bidder: 'avng', + bidder: 'advangelists', params: { pubid: '8537f00948fc37cc03c5f0f88e198a76', placement: 1234, diff --git a/modules/adxcgBidAdapter.js b/modules/adxcgBidAdapter.js index 73b70fe8e72..23808fa3be7 100644 --- a/modules/adxcgBidAdapter.js +++ b/modules/adxcgBidAdapter.js @@ -10,6 +10,7 @@ import includes from 'core-js/library/fn/array/includes' * updated to latest prebid repo on 2017.10.20 * updated for gdpr compliance on 2018.05.22 -requires gdpr compliance module * updated to pass aditional auction and impression level parameters. added pass for video targeting parameters + * updated to fix native support for image width/height and icon 2019.03.17 */ const BIDDER_CODE = 'adxcg' @@ -210,8 +211,10 @@ export const spec = { let nativeResponse = serverResponseOneItem.nativeResponse bid['native'] = { - clickUrl: encodeURIComponent(nativeResponse.link.url), - impressionTrackers: nativeResponse.imptrackers + clickUrl: nativeResponse.link.url, + impressionTrackers: nativeResponse.imptrackers, + clickTrackers: nativeResponse.clktrackers, + javascriptTrackers: nativeResponse.jstrackers } nativeResponse.assets.forEach(asset => { @@ -220,7 +223,19 @@ export const spec = { } if (asset.img && asset.img.url) { - bid['native'].image = asset.img.url + let nativeImage = {} + nativeImage.url = asset.img.url + nativeImage.height = asset.img.h + nativeImage.width = asset.img.w + bid['native'].image = nativeImage + } + + if (asset.icon && asset.icon.url) { + let nativeIcon = {} + nativeIcon.url = asset.icon.url + nativeIcon.height = asset.icon.h + nativeIcon.width = asset.icon.w + bid['native'].icon = nativeIcon } if (asset.data && asset.data.label === 'DESC' && asset.data.value) { diff --git a/modules/ajaBidAdapter.js b/modules/ajaBidAdapter.js index 4cbe9a840ea..5b826d1e725 100644 --- a/modules/ajaBidAdapter.js +++ b/modules/ajaBidAdapter.js @@ -1,7 +1,7 @@ import { Renderer } from '../src/Renderer'; import * as utils from '../src/utils'; import { registerBidder } from '../src/adapters/bidderFactory'; -import { VIDEO, BANNER } from '../src/mediaTypes'; +import { VIDEO, BANNER, NATIVE } from '../src/mediaTypes'; const BIDDER_CODE = 'aja'; const URL = '//ad.as.amanad.adtdp.com/v2/prebid'; @@ -14,7 +14,7 @@ const AD_TYPE = { export const spec = { code: BIDDER_CODE, - supportedMediaTypes: [VIDEO, BANNER], + supportedMediaTypes: [VIDEO, BANNER, NATIVE], isBidRequestValid: function(bid) { return !!(bid.params.asi); @@ -86,6 +86,41 @@ export const spec = { } catch (error) { utils.logError('Error appending tracking pixel', error); } + } else if (AD_TYPE.NATIVE === ad.ad_type) { + const nativeAds = ad.native.template_and_ads.ads; + + nativeAds.forEach(nativeAd => { + const assets = nativeAd.assets; + + Object.assign(bid, { + mediaType: NATIVE + }); + + bid.native = { + title: assets.title, + body: assets.description, + cta: assets.cta_text, + sponsoredBy: assets.sponsor, + clickUrl: assets.lp_link, + impressionTrackers: nativeAd.imps, + }; + + if (assets.img_main !== undefined) { + bid.native.image = { + url: assets.img_main, + width: parseInt(assets.img_main_width, 10), + height: parseInt(assets.img_main_height, 10) + }; + } + + if (assets.img_icon !== undefined) { + bid.native.icon = { + url: assets.img_icon, + width: parseInt(assets.img_icon_width, 10), + height: parseInt(assets.img_icon_height, 10) + }; + } + }); } return [bid]; diff --git a/modules/ajaBidAdapter.md b/modules/ajaBidAdapter.md index 6e513b94ff0..4f5aa7074cb 100644 --- a/modules/ajaBidAdapter.md +++ b/modules/ajaBidAdapter.md @@ -11,11 +11,11 @@ Connects to Aja exchange for bids. Aja bid adapter supports Banner and Outstream Video. # Test Parameters -``` +```js var adUnits = [ // Banner adUnit { - code: 'banner-div', + code: 'prebid_banner', mediaTypes: { banner: { sizes: [ @@ -26,13 +26,13 @@ var adUnits = [ bids: [{ bidder: 'aja', params: { - asi: 'szs4htFiR' + asi: 'tk82gbLmg' } }] }, // Video outstream adUnit { - code: 'video-outstream', + code: 'prebid_video', mediaTypes: { video: { context: 'outstream', @@ -42,7 +42,45 @@ var adUnits = [ bids: [{ bidder: 'aja', params: { - asi: 'Kp2O2tFig' + asi: '1-KwEG_iR' + } + }] + }, + // Native adUnit + { + code: 'prebid_native', + mediaTypes: { + native: { + image: { + required: true, + sendId: false + }, + title: { + required: true, + sendId: true + }, + sponsoredBy: { + required: false, + sendId: true + }, + clickUrl: { + required: false, + sendId: true + }, + body: { + required: false, + sendId: true + }, + icon: { + required: false, + sendId: false + } + } + }, + bids: [{ + bidder: 'aja', + params: { + asi: 'qxueUGliR' } }] } diff --git a/modules/brightcomBidAdapter.js b/modules/brightcomBidAdapter.js new file mode 100644 index 00000000000..626aa99f5de --- /dev/null +++ b/modules/brightcomBidAdapter.js @@ -0,0 +1,246 @@ +import * as utils from '../src/utils'; +import * as url from '../src/url'; +import { registerBidder } from '../src/adapters/bidderFactory'; +import { BANNER } from '../src/mediaTypes'; +import { config } from '../src/config'; + +const BIDDER_CODE = 'brightcom'; +const URL = 'https://brightcombid.marphezis.com/hb'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs +}; + +function buildRequests(bidReqs, bidderRequest) { + try { + let referrer = ''; + if (bidderRequest && bidderRequest.refererInfo) { + referrer = bidderRequest.refererInfo.referer; + } + const brightcomImps = []; + const publisherId = utils.getBidIdParameter('publisherId', bidReqs[0].params); + utils._each(bidReqs, function (bid) { + bid.sizes = ((utils.isArray(bid.sizes) && utils.isArray(bid.sizes[0])) ? bid.sizes : [bid.sizes]); + bid.sizes = bid.sizes.filter(size => utils.isArray(size)); + const processedSizes = bid.sizes.map(size => ({w: parseInt(size[0], 10), h: parseInt(size[1], 10)})); + + const element = document.getElementById(bid.adUnitCode); + const minSize = _getMinSize(processedSizes); + const viewabilityAmount = _isViewabilityMeasurable(element) + ? _getViewability(element, utils.getWindowTop(), minSize) + : 'na'; + const viewabilityAmountRounded = isNaN(viewabilityAmount) ? viewabilityAmount : Math.round(viewabilityAmount); + + const imp = { + id: bid.bidId, + banner: { + format: processedSizes, + ext: { + viewability: viewabilityAmountRounded + } + }, + tagid: String(bid.adUnitCode) + }; + const bidFloor = utils.getBidIdParameter('bidFloor', bid.params); + if (bidFloor) { + imp.bidfloor = bidFloor; + } + brightcomImps.push(imp); + }); + const brightcomBidReq = { + id: utils.getUniqueIdentifierStr(), + imp: brightcomImps, + site: { + domain: url.parse(referrer).host, + page: referrer, + publisher: { + id: publisherId + } + }, + device: { + devicetype: _getDeviceType(), + w: screen.width, + h: screen.height + }, + tmax: config.getConfig('bidderTimeout') + }; + + return { + method: 'POST', + url: URL, + data: JSON.stringify(brightcomBidReq), + options: {contentType: 'text/plain', withCredentials: false} + }; + } catch (e) { + utils.logError(e, {bidReqs, bidderRequest}); + } +} + +function isBidRequestValid(bid) { + if (bid.bidder !== BIDDER_CODE || typeof bid.params === 'undefined') { + return false; + } + + if (typeof bid.params.publisherId === 'undefined') { + return false; + } + + return true; +} + +function interpretResponse(serverResponse) { + if (!serverResponse.body || typeof serverResponse.body != 'object') { + utils.logWarn('Brightcom server returned empty/non-json response: ' + JSON.stringify(serverResponse.body)); + return []; + } + const { body: {id, seatbid} } = serverResponse; + try { + const brightcomBidResponses = []; + if (id && + seatbid && + seatbid.length > 0 && + seatbid[0].bid && + seatbid[0].bid.length > 0) { + seatbid[0].bid.map(brightcomBid => { + brightcomBidResponses.push({ + requestId: brightcomBid.impid, + cpm: parseFloat(brightcomBid.price), + width: parseInt(brightcomBid.w), + height: parseInt(brightcomBid.h), + creativeId: brightcomBid.crid || brightcomBid.id, + currency: 'USD', + netRevenue: true, + mediaType: BANNER, + ad: _getAdMarkup(brightcomBid), + ttl: 60 + }); + }); + } + return brightcomBidResponses; + } catch (e) { + utils.logError(e, {id, seatbid}); + } +} + +// Don't do user sync for now +function getUserSyncs(syncOptions, responses, gdprConsent) { + return []; +} + +function _isMobile() { + return (/(ios|ipod|ipad|iphone|android)/i).test(navigator.userAgent); +} + +function _isConnectedTV() { + return (/(smart[-]?tv|hbbtv|appletv|googletv|hdmi|netcast\.tv|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b)/i).test(navigator.userAgent); +} + +function _getDeviceType() { + return _isMobile() ? 1 : _isConnectedTV() ? 3 : 2; +} + +function _getAdMarkup(bid) { + let adm = bid.adm; + if ('nurl' in bid) { + adm += utils.createTrackPixelHtml(bid.nurl); + } + return adm; +} + +function _isViewabilityMeasurable(element) { + return !_isIframe() && element !== null; +} + +function _getViewability(element, topWin, { w, h } = {}) { + return utils.getWindowTop().document.visibilityState === 'visible' + ? _getPercentInView(element, topWin, { w, h }) + : 0; +} + +function _isIframe() { + try { + return utils.getWindowSelf() !== utils.getWindowTop(); + } catch (e) { + return true; + } +} + +function _getMinSize(sizes) { + return sizes.reduce((min, size) => size.h * size.w < min.h * min.w ? size : min); +} + +function _getBoundingBox(element, { w, h } = {}) { + let { width, height, left, top, right, bottom } = element.getBoundingClientRect(); + + if ((width === 0 || height === 0) && w && h) { + width = w; + height = h; + right = left + w; + bottom = top + h; + } + + return { width, height, left, top, right, bottom }; +} + +function _getIntersectionOfRects(rects) { + const bbox = { + left: rects[0].left, + right: rects[0].right, + top: rects[0].top, + bottom: rects[0].bottom + }; + + for (let i = 1; i < rects.length; ++i) { + bbox.left = Math.max(bbox.left, rects[i].left); + bbox.right = Math.min(bbox.right, rects[i].right); + + if (bbox.left >= bbox.right) { + return null; + } + + bbox.top = Math.max(bbox.top, rects[i].top); + bbox.bottom = Math.min(bbox.bottom, rects[i].bottom); + + if (bbox.top >= bbox.bottom) { + return null; + } + } + + bbox.width = bbox.right - bbox.left; + bbox.height = bbox.bottom - bbox.top; + + return bbox; +} + +function _getPercentInView(element, topWin, { w, h } = {}) { + const elementBoundingBox = _getBoundingBox(element, { w, h }); + + // Obtain the intersection of the element and the viewport + const elementInViewBoundingBox = _getIntersectionOfRects([ { + left: 0, + top: 0, + right: topWin.innerWidth, + bottom: topWin.innerHeight + }, elementBoundingBox ]); + + let elementInViewArea, elementTotalArea; + + if (elementInViewBoundingBox !== null) { + // Some or all of the element is in view + elementInViewArea = elementInViewBoundingBox.width * elementInViewBoundingBox.height; + elementTotalArea = elementBoundingBox.width * elementBoundingBox.height; + + return ((elementInViewArea / elementTotalArea) * 100); + } + + // No overlap between element and the viewport; therefore, the element + // lies completely out of view + return 0; +} + +registerBidder(spec); diff --git a/modules/brightcomBidAdapter.md b/modules/brightcomBidAdapter.md new file mode 100644 index 00000000000..badc6ea94a4 --- /dev/null +++ b/modules/brightcomBidAdapter.md @@ -0,0 +1,38 @@ +# Overview + +``` +Module Name: Brightcom Bid Adapter +Module Type: Bidder Adapter +Maintainer: vladislavy@brightcom.com +``` + +# Description + +Brightcom's adapter integration to the Prebid library. + +# Test Parameters + +``` +var adUnits = [ + { + code: 'test-leaderboard', + sizes: [[728, 90]], + bids: [{ + bidder: 'brightcom', + params: { + publisherId: 2141020, + bidFloor: 0.01 + } + }] + }, { + code: 'test-banner', + sizes: [[300, 250]], + bids: [{ + bidder: 'brightcom', + params: { + publisherId: 2141020 + } + }] + } +] +``` diff --git a/modules/consumableBidAdapter.js b/modules/consumableBidAdapter.js index 9374be62452..d462acaee59 100644 --- a/modules/consumableBidAdapter.js +++ b/modules/consumableBidAdapter.js @@ -28,10 +28,7 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ - buildRequests: function(validBidRequests) { - // Do we need to group by bidder? i.e. to make multiple requests for - // different endpoints. - + buildRequests: function(validBidRequests, bidderRequest) { let ret = { method: 'POST', url: '', @@ -50,14 +47,21 @@ export const spec = { const data = Object.assign({ placements: [], time: Date.now(), - user: {}, url: utils.getTopWindowUrl(), referrer: document.referrer, - enableBotFiltering: true, - includePricingData: true, - parallel: true + source: [{ + 'name': 'prebidjs', + 'version': '$prebid.version$' + }] }, validBidRequests[0].params); + if (bidderRequest && bidderRequest.gdprConsent) { + data.gdpr = { + consent: bidderRequest.gdprConsent.consentString, + applies: (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') ? bidderRequest.gdprConsent.gdprApplies : true + }; + } + validBidRequests.map(bid => { const placement = Object.assign({ divName: bid.bidId, @@ -123,12 +127,16 @@ export const spec = { return bidResponses; }, - getUserSyncs: function(syncOptions) { + getUserSyncs: function(syncOptions, serverResponses) { if (syncOptions.iframeEnabled) { return [{ type: 'iframe', url: '//sync.serverbid.com/ss/' + siteId + '.html' }]; + } + + if (syncOptions.pixelEnabled && serverResponses.length > 0) { + return serverResponses[0].body.pixels; } else { utils.logWarn(bidder + ': Please enable iframe based user syncing.'); } @@ -192,12 +200,7 @@ function getSize(sizes) { } function retrieveAd(decision, unitId, unitName) { - let oad = decision.contents && decision.contents[0] && decision.contents[0].body + utils.createTrackPixelHtml(decision.impressionUrl); - let cb = Math.round(new Date().getTime()); - let ad = '' + oad; - ad += ''; - ad += ''; - ad += '' + let ad = decision.contents && decision.contents[0] && decision.contents[0].body + utils.createTrackPixelHtml(decision.impressionUrl); return ad; } diff --git a/modules/fintezaAnalyticsAdapter.js b/modules/fintezaAnalyticsAdapter.js new file mode 100644 index 00000000000..2b5cd0421c2 --- /dev/null +++ b/modules/fintezaAnalyticsAdapter.js @@ -0,0 +1,406 @@ +import { ajax } from '../src/ajax'; +import adapter from '../src/AnalyticsAdapter'; +import adapterManager from '../src/adapterManager'; +import * as utils from '../src/utils'; +import { parse as parseURL } from '../src/url'; + +const CONSTANTS = require('../src/constants.json'); + +const ANALYTICS_TYPE = 'endpoint'; +const FINTEZA_HOST = 'https://content.mql5.com/tr'; +const BID_REQUEST_TRACK = 'Bid Request %BIDDER%'; +const BID_RESPONSE_TRACK = 'Bid Response %BIDDER%'; +const BID_TIMEOUT_TRACK = 'Bid Timeout %BIDDER%'; +const BID_WON_TRACK = 'Bid Won %BIDDER%'; + +const FIRST_VISIT_DATE = '_fz_fvdt'; +const SESSION_ID = '_fz_ssn'; +const SESSION_DURATION = 30 * 60 * 1000; +const SESSION_RAND_PART = 9; +const TRACK_TIME_KEY = '_fz_tr'; + +function getPageInfo() { + const pageInfo = { + domain: window.location.hostname, + } + + if (document.referrer) { + pageInfo.referrerDomain = parseURL(document.referrer).hostname; + } + + return pageInfo; +} + +function initFirstVisit() { + let now; + let visitDate; + let cookies; + + try { + cookies = parseCookies(document.cookie); + } catch (a) { + cookies = {}; + } + + visitDate = cookies[ FIRST_VISIT_DATE ]; + + if (!visitDate) { + now = new Date(); + + visitDate = parseInt(now.getTime() / 1000, 10); + + now.setFullYear(now.getFullYear() + 20); + + try { + document.cookie = FIRST_VISIT_DATE + '=' + visitDate + '; path=/; expires=' + now.toUTCString(); + } catch (e) {} + } + + return visitDate; +} + +function trim(string) { + if (string.trim) { + return string.trim(); + } + return string.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); +} + +function parseCookies(cookie) { + let values = {}; + let arr, item; + let param, value; + let i, j; + + if (!cookie) { + return {}; + } + + arr = cookie.split(';'); + + for (i = 0, j = arr.length; i < j; i++) { + item = arr[ i ]; + if (!item) { + continue; + } + + param = item.split('='); + if (param.length <= 1) { + continue; + } + + value = decodeURIComponent(param[0]); + value = trim(value); + + values[value] = decodeURIComponent(param[1]); + } + + return values; +} + +function getRandAsStr(digits) { + let str = ''; + let rand = 0; + let i; + + digits = digits || 4; + + for (i = 0; i < digits; i++) { + rand = (Math.random() * 10) >>> 0; + str += '' + rand; + } + + return str; +} + +function getSessionBegin(session) { + if (!session || (typeof session !== 'string')) { + return 0; + } + + const len = session.length; + if (len && len <= SESSION_RAND_PART) { + return 0; + } + + const timestamp = session.substring(0, len - SESSION_RAND_PART); + + return parseInt(timestamp, 10); +} + +function initSession() { + const now = new Date(); + const expires = new Date(now.getTime() + SESSION_DURATION); + const timestamp = Math.floor(now.getTime() / 1000); + let begin = 0; + let cookies; + let sessionId; + let sessionDuration; + let isNew = false; + + try { + cookies = parseCookies(document.cookie); + } catch (a) { + cookies = {}; + } + + sessionId = cookies[ SESSION_ID ]; + + if (!sessionId || + !checkSessionByExpires() || + !checkSessionByReferer() || + !checkSessionByDay()) { + sessionId = '' + timestamp + getRandAsStr(SESSION_RAND_PART); + begin = timestamp; + + isNew = true; + } else { + begin = getSessionBegin(sessionId); + } + + if (begin > 0) { + sessionDuration = Math.floor(timestamp - begin); + } else { + sessionDuration = -1; + } + + try { + document.cookie = SESSION_ID + '=' + sessionId + '; path=/; expires=' + expires.toUTCString(); + } catch (e) {} + + return { + isNew: isNew, + id: sessionId, + duration: sessionDuration + }; +} + +function checkSessionByExpires() { + const timestamp = getTrackRequestLastTime(); + const now = new Date().getTime(); + + if (now > timestamp + SESSION_DURATION) { + return false; + } + return true; +} + +function checkSessionByReferer() { + const referrer = fntzAnalyticsAdapter.context.pageInfo.referrerDomain; + const domain = fntzAnalyticsAdapter.context.pageInfo.domain; + + return referrer === '' || domain === referrer; +} + +function checkSessionByDay() { + let last = getTrackRequestLastTime(); + if (last) { + last = new Date(last); + const now = new Date(); + + return last.getUTCDate() === now.getUTCDate() && + last.getUTCMonth() === now.getUTCMonth() && + last.getUTCFullYear() === now.getUTCFullYear(); + } + + return false; +} + +function saveTrackRequestTime() { + const now = new Date().getTime(); + const expires = new Date(now + SESSION_DURATION); + + try { + if (window.localStorage) { + window.localStorage.setItem(TRACK_TIME_KEY, now.toString()); + } else { + document.cookie = TRACK_TIME_KEY + '=' + now + '; path=/; expires=' + expires.toUTCString(); + } + } catch (a) {} +} + +function getTrackRequestLastTime() { + let cookie; + + try { + if (window.localStorage) { + return parseInt( + window.localStorage.getItem(TRACK_TIME_KEY) || 0, + 10, + ); + } + + cookie = parseCookies(document.cookie); + cookie = cookie[ TRACK_TIME_KEY ]; + if (cookie) { + return parseInt(cookie, 10); + } + } catch (e) {} + + return 0; +} + +function getAntiCacheParam() { + const date = new Date(); + const rand = (Math.random() * 99999 + 1) >>> 0; + + return ([ date.getTime(), rand ].join('')); +} + +function replaceBidder(str, bidder) { + let _str = str; + _str = _str.replace(/\%bidder\%/, bidder.toLowerCase()); + _str = _str.replace(/\%BIDDER\%/, bidder.toUpperCase()); + _str = _str.replace(/\%Bidder\%/, bidder.charAt(0).toUpperCase() + bidder.slice(1).toLowerCase()); + + return _str; +} + +function prepareBidRequestedParams(args) { + return [{ + event: encodeURIComponent(replaceBidder(fntzAnalyticsAdapter.context.bidRequestTrack, args.bidderCode)), + ref: encodeURIComponent(window.location.href), + }]; +} + +function prepareBidResponseParams(args) { + return [{ + event: encodeURIComponent(replaceBidder(fntzAnalyticsAdapter.context.bidResponseTrack, args.bidderCode)), + c1_value: args.timeToRespond, + c1_unit: 'ms', + c2_value: args.cpm, + c2_unit: 'usd', + }]; +} + +function prepareBidWonParams(args) { + return [{ + event: encodeURIComponent(replaceBidder(fntzAnalyticsAdapter.context.bidWonTrack, args.bidderCode)), + c1_value: args.cpm, + c1_unit: 'usd', + }]; +} + +function prepareBidTimeoutParams(args) { + return args.map(function(bid) { + return { + event: encodeURIComponent(replaceBidder(fntzAnalyticsAdapter.context.bidTimeoutTrack, bid.bidder)), + c1_value: bid.timeout, + c1_unit: 'ms', + }; + }) +} + +function prepareTrackData(evtype, args) { + let prepareParams = null; + + switch (evtype) { + case CONSTANTS.EVENTS.BID_REQUESTED: + prepareParams = prepareBidRequestedParams; + break; + case CONSTANTS.EVENTS.BID_RESPONSE: + prepareParams = prepareBidResponseParams; + break; + case CONSTANTS.EVENTS.BID_WON: + prepareParams = prepareBidWonParams; + break; + case CONSTANTS.EVENTS.BID_TIMEOUT: + prepareParams = prepareBidTimeoutParams; + break; + } + + if (!prepareParams) { return null; } + + const data = prepareParams(args); + + if (!data) { return null; } + + const session = initSession(); + + return data.map(d => { + const trackData = Object.assign(d, { + id: fntzAnalyticsAdapter.context.id, + ref: encodeURIComponent(window.location.href), + title: encodeURIComponent(document.title), + scr_res: fntzAnalyticsAdapter.context.screenResolution, + fv_date: fntzAnalyticsAdapter.context.firstVisit, + ac: getAntiCacheParam(), + }) + + if (session.id) { + trackData.ssn = session.id; + } + if (session.isNew) { + session.isNew = false; + trackData.ssn_start = 1; + } + trackData.ssn_dr = session.duration; + + return trackData; + }); +} + +function sendTrackRequest(trackData) { + try { + ajax( + fntzAnalyticsAdapter.context.host, + null, // Callback + trackData, + { + method: 'GET', + contentType: 'application/x-www-form-urlencoded', + // preflight: true, + }, + ); + saveTrackRequestTime(); + } catch (err) { + utils.logError('Error on send data: ', err); + } +} + +const fntzAnalyticsAdapter = Object.assign( + adapter({ + FINTEZA_HOST, + ANALYTICS_TYPE + }), + { + track({ eventType, args }) { + if (typeof args !== 'undefined') { + const trackData = prepareTrackData(eventType, args); + if (!trackData) { return; } + + trackData.forEach(sendTrackRequest); + } + } + } +); + +fntzAnalyticsAdapter.originEnableAnalytics = fntzAnalyticsAdapter.enableAnalytics; + +fntzAnalyticsAdapter.enableAnalytics = function (config) { + if (!config.options.id) { + utils.logError('Client ID (id) option is not defined. Analytics won\'t work'); + return; + } + + fntzAnalyticsAdapter.context = { + host: config.options.host || FINTEZA_HOST, + id: config.options.id, + bidRequestTrack: config.options.bidRequestTrack || BID_REQUEST_TRACK, + bidResponseTrack: config.options.bidResponseTrack || BID_RESPONSE_TRACK, + bidTimeoutTrack: config.options.bidTimeoutTrack || BID_TIMEOUT_TRACK, + bidWonTrack: config.options.bidWonTrack || BID_WON_TRACK, + firstVisit: initFirstVisit(), + screenResolution: `${window.screen.width}x${window.screen.height}`, + pageInfo: getPageInfo(), + }; + + fntzAnalyticsAdapter.originEnableAnalytics(config); +}; + +adapterManager.registerAnalyticsAdapter({ + adapter: fntzAnalyticsAdapter, + code: 'finteza' +}); + +export default fntzAnalyticsAdapter; diff --git a/modules/fintezaAnalyticsAdapter.md b/modules/fintezaAnalyticsAdapter.md new file mode 100644 index 00000000000..22525f55366 --- /dev/null +++ b/modules/fintezaAnalyticsAdapter.md @@ -0,0 +1,28 @@ +# Overview + +``` +Module Name: Finteza Analytics Adapter +Module Type: Analytics Adapter +Maintainer: renat@finteza.com +``` + +# Description + +The Finteza adapter for integration with Prebid is an analytics tool for publishers who use the Header Bidding technology. The adapter tracks auction opening, offer sending to advertisers, receipt of bids by the publisher and auction winner selection. All tracks are sent to Finteza and enable visual advertiser quality evaluation: how many offers partners accept, what prices they provide, how fast they respond and how often their bids win. + +For more information, visit the [official Finteza website](https://www.finteza.com/). + +# Test Parameters + +``` +{ + provider: 'finteza', + options: { + id: 'xxxxx', // Website ID (required) + bidRequestTrack: 'Bid Request %BIDDER%', + bidResponseTrack: 'Bid Response %BIDDER%', + bidTimeoutTrack: 'Bid Timeout %BIDDER%', + bidWonTrack: 'Bid Won %BIDDER%' + } +} +``` diff --git a/modules/freeWheelAdserverVideo.js b/modules/freeWheelAdserverVideo.js index 01ed145b00b..c81f3356d71 100644 --- a/modules/freeWheelAdserverVideo.js +++ b/modules/freeWheelAdserverVideo.js @@ -4,10 +4,10 @@ import { registerVideoSupport } from '../src/adServerManager'; import { auctionManager } from '../src/auctionManager'; -import { groupBy, deepAccess, logError } from '../src/utils'; +import { groupBy, deepAccess, logError, compareOn } from '../src/utils'; import { config } from '../src/config'; import { ADPOD } from '../src/mediaTypes'; -import { initAdpodHooks, TARGETING_KEY_PB_CAT_DUR, TARGETING_KEY_CACHE_ID, callPrebidCacheAfterAuction } from './adpod'; +import { initAdpodHooks, TARGETING_KEY_PB_CAT_DUR, TARGETING_KEY_CACHE_ID, callPrebidCacheAfterAuction, sortByPricePerSecond } from './adpod'; import { getHook } from '../src/hook'; export function notifyTranslationModule(fn) { @@ -37,7 +37,7 @@ export function getTargeting({codes, callback} = {}) { let bids = getBidsForAdpod(bidsReceived, adPodAdUnits); bids = (competiveExclusionEnabled || deferCachingEnabled) ? getExclusiveBids(bids) : bids; - bids.sort(compareOn('cpm')); + bids.sort(sortByPricePerSecond); let targeting = {}; if (deferCachingEnabled === false) { @@ -119,18 +119,6 @@ function getAdPodAdUnits(codes) { .filter((adUnit) => (codes.length > 0) ? codes.indexOf(adUnit.code) != -1 : true); } -function compareOn(property) { - return function compare(a, b) { - if (a[property] < b[property]) { - return 1; - } - if (a[property] > b[property]) { - return -1; - } - return 0; - } -} - /** * This function removes bids of same freewheel category. It will be used when competitive exclusion is enabled. * @param {Array[Object]} bidsReceived diff --git a/modules/openxBidAdapter.js b/modules/openxBidAdapter.js index e465e44b25b..4671f054042 100644 --- a/modules/openxBidAdapter.js +++ b/modules/openxBidAdapter.js @@ -116,6 +116,11 @@ function createBannerBidResponses(oxResponseObj, {bids, startTime}) { } bidResponse.ts = adUnit.ts; + bidResponse.meta = {}; + if (adUnit.brand_id) { + bidResponse.meta.brandId = adUnit.brand_id; + } + bidResponses.push(bidResponse); registerBeacon(BANNER, adUnit, startTime); diff --git a/modules/optimeraBidAdapter.md b/modules/optimeraBidAdapter.md index 84df1d1ad07..909c0a46cd6 100644 --- a/modules/optimeraBidAdapter.md +++ b/modules/optimeraBidAdapter.md @@ -34,3 +34,23 @@ Module that adds ad placement visibility scores for DFP. }] }]; ``` + +# AppNexus Issue +There is an issue where the plugin sometimes doesn't return impressions with AppNexus. + +There is an open issue here: [#3597](https://github.com/prebid/Prebid.js/issues/3597) + +## Configuration Workaround + +Optimera's configuration requires the use of size 0,0 which, in some instances, causes the AppNexus ad server to respond to ad requests but not fill impressions. AppNexus and vendors using the AppNexus ad server should monitor 3rd party numbers to ensure there is no decline in fill rate. + +Configuration Example: + +``` +code: ‘leaderboard', +mediaTypes: { + banner: { + sizes: [[970, 250],[970, 90],[970, 66],[728, 90],[320, 50],[320, 100],[300, 250],[0, 0]], + } +} +``` diff --git a/modules/orbidderBidAdapter.js b/modules/orbidderBidAdapter.js index de365fe87de..e316f3ef212 100644 --- a/modules/orbidderBidAdapter.js +++ b/modules/orbidderBidAdapter.js @@ -18,6 +18,7 @@ export const spec = { return !!(bid.sizes && bid.bidId && bid.params && (bid.params.accountId && (typeof bid.params.accountId === 'string')) && (bid.params.placementId && (typeof bid.params.placementId === 'string')) && + ((typeof bid.params.bidfloor === 'undefined') || (typeof bid.params.bidfloor === 'number')) && ((typeof bid.params.keyValues === 'undefined') || (typeof bid.params.keyValues === 'object'))); }, diff --git a/modules/otmBidAdapter.js b/modules/otmBidAdapter.js index 78015d69594..57ac414c7b6 100644 --- a/modules/otmBidAdapter.js +++ b/modules/otmBidAdapter.js @@ -1,5 +1,5 @@ -import {BANNER} from '../src/mediaTypes'; -import {registerBidder} from '../src/adapters/bidderFactory'; +import {BANNER} from 'src/mediaTypes'; +import {registerBidder} from 'src/adapters/bidderFactory'; export const spec = { code: 'otm', @@ -9,10 +9,11 @@ export const spec = { }, buildRequests: function (bidRequests) { const requests = bidRequests.map(function (bid) { + const size = getMaxPrioritySize(bid.sizes); const params = { tz: getTz(), - w: bid.sizes[0][0], - h: bid.sizes[0][1], + w: size[0], + h: size[1], s: bid.params.tid, bidid: bid.bidId, transactionid: bid.transactionId, @@ -57,4 +58,38 @@ function getTz() { return new Date().getTimezoneOffset(); } +function getMaxPrioritySize(sizes) { + var maxPrioritySize = null; + + const sizesByPriority = [ + [300, 250], + [240, 400], + [728, 90], + [300, 600], + [970, 250], + [300, 50], + [320, 100] + ]; + + const sizeToString = (size) => { + return size[0] + 'x' + size[1]; + }; + + const sizesAsString = sizes.map(sizeToString); + + sizesByPriority.forEach(size => { + if (!maxPrioritySize) { + if (sizesAsString.indexOf(sizeToString(size)) !== -1) { + maxPrioritySize = size; + } + } + }); + + if (maxPrioritySize) { + return maxPrioritySize; + } else { + return sizes[0]; + } +} + registerBidder(spec); diff --git a/modules/ozoneBidAdapter.js b/modules/ozoneBidAdapter.js index 7a7033a79ac..406bffdf4df 100644 --- a/modules/ozoneBidAdapter.js +++ b/modules/ozoneBidAdapter.js @@ -7,7 +7,7 @@ const BIDDER_CODE = 'ozone'; const OZONEURI = 'https://elb.the-ozone-project.com/openrtb2/auction'; const OZONECOOKIESYNC = 'https://elb.the-ozone-project.com/static/load-cookie.html'; -const OZONEVERSION = '1.4.4'; +const OZONEVERSION = '1.4.7'; export const spec = { code: BIDDER_CODE, @@ -18,72 +18,78 @@ export const spec = { */ isBidRequestValid(bid) { if (!(bid.params.hasOwnProperty('placementId'))) { - utils.logInfo('OZONE BID ADAPTER VALIDATION FAILED : missing placementId : siteId, placementId and publisherId are REQUIRED'); + utils.logInfo('OZONE: OZONE BID ADAPTER VALIDATION FAILED : missing placementId : siteId, placementId and publisherId are REQUIRED'); return false; } if (!(bid.params.placementId).toString().match(/^[0-9]{10}$/)) { - utils.logInfo('OZONE BID ADAPTER VALIDATION FAILED : placementId must be exactly 10 numeric characters'); + utils.logInfo('OZONE: OZONE BID ADAPTER VALIDATION FAILED : placementId must be exactly 10 numeric characters'); return false; } if (!(bid.params.hasOwnProperty('publisherId'))) { - utils.logInfo('OZONE BID ADAPTER VALIDATION FAILED : missing publisherId : siteId, placementId and publisherId are REQUIRED'); + utils.logInfo('OZONE: OZONE BID ADAPTER VALIDATION FAILED : missing publisherId : siteId, placementId and publisherId are REQUIRED'); return false; } if (!(bid.params.publisherId).toString().match(/^[a-zA-Z0-9\-]{12}$/)) { - utils.logInfo('OZONE BID ADAPTER VALIDATION FAILED : publisherId must be exactly 12 alphanumieric characters including hyphens'); + utils.logInfo('OZONE: OZONE BID ADAPTER VALIDATION FAILED : publisherId must be exactly 12 alphanumieric characters including hyphens'); return false; } if (!(bid.params.hasOwnProperty('siteId'))) { - utils.logInfo('OZONE BID ADAPTER VALIDATION FAILED : missing siteId : siteId, placementId and publisherId are REQUIRED'); + utils.logInfo('OZONE: OZONE BID ADAPTER VALIDATION FAILED : missing siteId : siteId, placementId and publisherId are REQUIRED'); return false; } if (!(bid.params.siteId).toString().match(/^[0-9]{10}$/)) { - utils.logInfo('OZONE BID ADAPTER VALIDATION FAILED : siteId must be exactly 10 numeric characters'); + utils.logInfo('OZONE: OZONE BID ADAPTER VALIDATION FAILED : siteId must be exactly 10 numeric characters'); return false; } if (bid.params.hasOwnProperty('customData')) { if (typeof bid.params.customData !== 'object') { - utils.logInfo('OZONE BID ADAPTER VALIDATION FAILED : customData is not an object'); + utils.logInfo('OZONE: OZONE BID ADAPTER VALIDATION FAILED : customData is not an object'); return false; } } if (bid.params.hasOwnProperty('customParams')) { - utils.logInfo('OZONE BID ADAPTER VALIDATION FAILED : customParams should be renamed to customData'); + utils.logInfo('OZONE: OZONE BID ADAPTER VALIDATION FAILED : customParams should be renamed to customData'); return false; } if (bid.params.hasOwnProperty('ozoneData')) { if (typeof bid.params.ozoneData !== 'object') { - utils.logInfo('OZONE BID ADAPTER VALIDATION FAILED : ozoneData is not an object'); + utils.logInfo('OZONE: OZONE BID ADAPTER VALIDATION FAILED : ozoneData is not an object'); return false; } } if (bid.params.hasOwnProperty('lotameData')) { if (typeof bid.params.lotameData !== 'object') { - utils.logInfo('OZONE BID ADAPTER VALIDATION FAILED : lotameData is not an object'); + utils.logInfo('OZONE: OZONE BID ADAPTER VALIDATION FAILED : lotameData is not an object'); return false; } } return true; }, buildRequests(validBidRequests, bidderRequest) { - utils.logInfo('ozone v' + OZONEVERSION + ' validBidRequests', validBidRequests, 'bidderRequest', bidderRequest); - utils.logInfo('buildRequests setting auctionId', bidderRequest.auctionId); + utils.logInfo('OZONE: ozone v' + OZONEVERSION + ' validBidRequests', validBidRequests, 'bidderRequest', bidderRequest); + utils.logInfo('OZONE: buildRequests setting auctionId', bidderRequest.auctionId); let singleRequest = config.getConfig('ozone.singleRequest'); + singleRequest = singleRequest !== false; // undefined & true will be true - utils.logInfo('config ozone.singleRequest : ', singleRequest); + utils.logInfo('OZONE: config ozone.singleRequest : ', singleRequest); let htmlParams = validBidRequests[0].params; // the html page config params will be included in each element let ozoneRequest = {}; // we only want to set specific properties on this, not validBidRequests[0].params // ozoneRequest['id'] = utils.generateUUID(); delete ozoneRequest.test; // don't allow test to be set in the config - ONLY use $_GET['pbjs_debug'] if (bidderRequest.gdprConsent) { + utils.logInfo('OZONE: ADDING GDPR info'); ozoneRequest.regs = {}; ozoneRequest.regs.ext = {}; ozoneRequest.regs.ext.gdpr = bidderRequest.gdprConsent.gdprApplies === true ? 1 : 0; if (ozoneRequest.regs.ext.gdpr) { - ozoneRequest.regs.ext.consent = bidderRequest.gdprConsent.consentString; + ozoneRequest.user = {}; + ozoneRequest.user.ext = {'consent': bidderRequest.gdprConsent.consentString}; } + } else { + utils.logInfo('OZONE: WILL NOT ADD GDPR info'); } + ozoneRequest.device = {'w': window.innerWidth, 'h': window.innerHeight}; let tosendtags = validBidRequests.map(ozoneBidRequest => { var obj = {}; obj.id = ozoneBidRequest.bidId; // this causes a failure if we change it to something else @@ -95,25 +101,25 @@ export const spec = { /* NOTE - if there is sizes element in the config root then there will be a mediaTypes.banner element automatically generated for us, so this code is deprecated */ if (!ozoneBidRequest.hasOwnProperty('mediaTypes')) { if (ozoneBidRequest.hasOwnProperty('sizes')) { - utils.logInfo('no mediaTypes detected - will use the sizes array in the config root'); + utils.logInfo('OZONE: no mediaTypes detected - will use the sizes array in the config root'); arrBannerSizes = ozoneBidRequest.sizes; } else { - utils.logInfo('no mediaTypes detected, no sizes array in the config root either. Cannot set sizes for banner type'); + utils.logInfo('OZONE: no mediaTypes detected, no sizes array in the config root either. Cannot set sizes for banner type'); } } else { if (ozoneBidRequest.mediaTypes.hasOwnProperty(BANNER)) { arrBannerSizes = ozoneBidRequest.mediaTypes[BANNER].sizes; /* Note - if there is a sizes element in the config root it will be pushed into here */ - utils.logInfo('setting banner size from the mediaTypes.banner element for bidId ' + obj.id + ': ', arrBannerSizes); + utils.logInfo('OZONE: setting banner size from the mediaTypes.banner element for bidId ' + obj.id + ': ', arrBannerSizes); } // Video integration is not complete yet if (ozoneBidRequest.mediaTypes.hasOwnProperty(VIDEO)) { obj.video = ozoneBidRequest.mediaTypes[VIDEO]; - utils.logInfo('setting video object from the mediaTypes.video element: ' + obj.id + ':', obj.video); + utils.logInfo('OZONE: setting video object from the mediaTypes.video element: ' + obj.id + ':', obj.video); } // Native integration is not complete yet if (ozoneBidRequest.mediaTypes.hasOwnProperty(NATIVE)) { obj.native = ozoneBidRequest.mediaTypes[NATIVE]; - utils.logInfo('setting native object from the mediaTypes.native element: ' + obj.id + ':', obj.native); + utils.logInfo('OZONE: setting native object from the mediaTypes.native element: ' + obj.id + ':', obj.native); } } // build the banner request using banner sizes we found in either possible location: @@ -127,15 +133,10 @@ export const spec = { }) }; } - if (ozoneBidRequest.params.hasOwnProperty('placementId')) { - obj.placementId = (ozoneBidRequest.params.placementId).toString(); - } - if (ozoneBidRequest.params.hasOwnProperty('publisherId')) { - obj.publisherId = (ozoneBidRequest.params.publisherId).toString(); - } - if (ozoneBidRequest.params.hasOwnProperty('siteId')) { - obj.siteId = (ozoneBidRequest.params.siteId).toString(); - } + // these 3 MUST exist - we check them in the validation method + obj.placementId = (ozoneBidRequest.params.placementId).toString(); + obj.publisherId = (ozoneBidRequest.params.publisherId).toString(); + obj.siteId = (ozoneBidRequest.params.siteId).toString(); // build the imp['ext'] object obj.ext = {'prebid': {'storedrequest': {'id': (ozoneBidRequest.params.placementId).toString()}}, 'ozone': {}}; obj.ext.ozone.adUnitCode = ozoneBidRequest.adUnitCode; // eg. 'mpu' @@ -161,7 +162,7 @@ export const spec = { // utils.logInfo('_ozoneInternal is', _ozoneInternal); // return the single request object OR the array: if (singleRequest) { - utils.logInfo('buildRequests starting to generate response for a single request'); + utils.logInfo('OZONE: buildRequests starting to generate response for a single request'); ozoneRequest.id = bidderRequest.auctionId; // Unique ID of the bid request, provided by the exchange. ozoneRequest.auctionId = bidderRequest.auctionId; // not sure if this should be here? ozoneRequest.imp = tosendtags; @@ -172,21 +173,21 @@ export const spec = { data: JSON.stringify(ozoneRequest), bidderRequest: bidderRequest }; - utils.logInfo('buildRequests ozoneRequest for single = ', ozoneRequest); - utils.logInfo('buildRequests going to return for single: ', ret); + utils.logInfo('OZONE: buildRequests ozoneRequest for single = ', ozoneRequest); + utils.logInfo('OZONE: buildRequests going to return for single: ', ret); return ret; } // not single request - pull apart the tosendtags array & return an array of objects each containing one element in the imp array. let arrRet = tosendtags.map(imp => { - utils.logInfo('buildRequests starting to generate non-single response, working on imp : ', imp); + utils.logInfo('OZONE: buildRequests starting to generate non-single response, working on imp : ', imp); let ozoneRequestSingle = Object.assign({}, ozoneRequest); imp.ext.ozone.pageAuctionId = bidderRequest['auctionId']; // make a note in the ext object of what the original auctionId was, in the bidderRequest object ozoneRequestSingle.id = imp.ext.ozone.transactionId; // Unique ID of the bid request, provided by the exchange. ozoneRequestSingle.auctionId = imp.ext.ozone.transactionId; // not sure if this should be here? ozoneRequestSingle.imp = [imp]; ozoneRequestSingle.source = {'tid': imp.ext.ozone.transactionId}; - utils.logInfo('buildRequests ozoneRequestSingle (for non-single) = ', ozoneRequestSingle); + utils.logInfo('OZONE: buildRequests ozoneRequestSingle (for non-single) = ', ozoneRequestSingle); return { method: 'POST', url: OZONEURI, @@ -194,7 +195,7 @@ export const spec = { bidderRequest: bidderRequest }; }); - utils.logInfo('buildRequests going to return for non-single: ', arrRet); + utils.logInfo('OZONE: buildRequests going to return for non-single: ', arrRet); return arrRet; }, /** @@ -205,7 +206,7 @@ export const spec = { * @returns {*} */ interpretResponse(serverResponse, request) { - utils.logInfo('ozone v' + OZONEVERSION + ' interpretResponse', serverResponse, request); + utils.logInfo('OZONE: version' + OZONEVERSION + ' interpretResponse', serverResponse, request); serverResponse = serverResponse.body || {}; if (serverResponse.seatbid) { if (utils.isArray(serverResponse.seatbid)) { @@ -219,19 +220,19 @@ export const spec = { let {seat: winningSeat, bid: winningBid} = ozoneGetWinnerForRequestBid(thisBid, serverResponse.seatbid); if (winningBid == null) { - utils.logInfo('FAILED to get winning bid for bid : ', thisBid, 'will skip. Possibly a non-single request, which will be missing some bid IDs'); + utils.logInfo('OZONE: FAILED to get winning bid for bid : ', thisBid, 'will skip. Possibly a non-single request, which will be missing some bid IDs'); continue; } const {defaultWidth, defaultHeight} = defaultSize(arrRequestBids[i]); winningBid = ozoneAddStandardProperties(winningBid, defaultWidth, defaultHeight); - utils.logInfo('Going to add the adserverTargeting custom parameters for key: ', ozoneInternalKey); + utils.logInfo('OZONE: Going to add the adserverTargeting custom parameters for key: ', ozoneInternalKey); let adserverTargeting = {}; let allBidsForThisBidid = ozoneGetAllBidsForBidId(ozoneInternalKey, serverResponse.seatbid); // add all the winning & non-winning bids for this bidId: Object.keys(allBidsForThisBidid).forEach(function(bidderName, index, ar2) { - utils.logInfo('inside allBidsForThisBidid:foreach', bidderName, index, ar2, allBidsForThisBidid); + utils.logInfo('OZONE: inside allBidsForThisBidid:foreach', bidderName, index, ar2, allBidsForThisBidid); adserverTargeting['oz_' + bidderName] = bidderName; adserverTargeting['oz_' + bidderName + '_pb'] = String(allBidsForThisBidid[bidderName].price); adserverTargeting['oz_' + bidderName + '_crid'] = String(allBidsForThisBidid[bidderName].crid); @@ -246,14 +247,14 @@ export const spec = { adserverTargeting['oz_response_id'] = String(serverResponse.id); winningBid.adserverTargeting = adserverTargeting; - utils.logInfo('winner is', winningBid); + utils.logInfo('OZONE: winner is', winningBid); arrWinners.push(winningBid); - utils.logInfo('arrWinners is', arrWinners); + utils.logInfo('OZONE: arrWinners is', arrWinners); } let winnersClean = arrWinners.filter(w => { return (w.bidId); // will be cast to boolean }); - utils.logInfo('going to return winnersClean:', winnersClean); + utils.logInfo('OZONE: going to return winnersClean:', winnersClean); return winnersClean; } else { return []; @@ -326,7 +327,7 @@ export function ozoneGetWinnerForRequestBid(requestBid, serverResponseSeatBid) { * @returns {} = {ozone:{obj}, appnexus:{obj}, ... } */ export function ozoneGetAllBidsForBidId(matchBidId, serverResponseSeatBid) { - utils.logInfo('ozoneGetAllBidsForBidId - starting, with: ', matchBidId, serverResponseSeatBid); + utils.logInfo('OZONE: ozoneGetAllBidsForBidId - starting, with: ', matchBidId, serverResponseSeatBid); let objBids = {}; for (let j = 0; j < serverResponseSeatBid.length; j++) { let theseBids = serverResponseSeatBid[j].bid; @@ -338,7 +339,7 @@ export function ozoneGetAllBidsForBidId(matchBidId, serverResponseSeatBid) { } } } - utils.logInfo('ozoneGetAllBidsForBidId - going to return: ', objBids); + utils.logInfo('OZONE: ozoneGetAllBidsForBidId - going to return: ', objBids); return objBids; } @@ -379,4 +380,4 @@ export function getTestQuerystringValue() { } registerBidder(spec); -utils.logInfo('ozoneBidAdapter ended'); +utils.logInfo('OZONE: ozoneBidAdapter ended'); diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index b8da30021cd..d80d6c5c810 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -4,6 +4,7 @@ import { BANNER, VIDEO, NATIVE } from '../src/mediaTypes'; import {config} from '../src/config'; const BIDDER_CODE = 'pubmatic'; +const LOG_WARN_PREFIX = 'PubMatic: '; const ENDPOINT = '//hbopenbid.pubmatic.com/translator?source=prebid-client'; const USYNCURL = '//ads.pubmatic.com/AdServer/js/showad.js#PIX&kdntuid=1&p='; const DEFAULT_CURRENCY = 'USD'; @@ -175,7 +176,7 @@ function _getDomainFromURL(url) { function _parseSlotParam(paramName, paramValue) { if (!utils.isStr(paramValue)) { - paramValue && utils.logWarn('PubMatic: Ignoring param key: ' + paramName + ', expects string-value, found ' + typeof paramValue); + paramValue && utils.logWarn(LOG_WARN_PREFIX + 'Ignoring param key: ' + paramName + ', expects string-value, found ' + typeof paramValue); return UNDEFINED; } @@ -221,7 +222,7 @@ function _parseAdSlot(bid) { splits = slot.split('@'); if (splits.length != 2) { if (!(sizesArrayExists)) { - utils.logWarn('AdSlot Error: adSlot not in required format'); + utils.logWarn(LOG_WARN_PREFIX + 'AdSlot Error: adSlot not in required format'); return; } } @@ -229,7 +230,7 @@ function _parseAdSlot(bid) { if (splits.length > 1) { // i.e size is specified in adslot, so consider that and ignore sizes array splits = splits[1].split('x'); if (splits.length != 2) { - utils.logWarn('AdSlot Error: adSlot not in required format'); + utils.logWarn(LOG_WARN_PREFIX + 'AdSlot Error: adSlot not in required format'); return; } bid.params.width = parseInt(splits[0]); @@ -273,7 +274,7 @@ function _handleCustomParams(params, conf) { if (utils.isStr(value)) { conf[key] = value; } else { - utils.logWarn('PubMatic: Ignoring param : ' + key + ' with value : ' + CUSTOM_PARAMS[key] + ', expects string-value, found ' + typeof value); + utils.logWarn(LOG_WARN_PREFIX + 'Ignoring param : ' + key + ' with value : ' + CUSTOM_PARAMS[key] + ', expects string-value, found ' + typeof value); } } } @@ -311,25 +312,25 @@ function _checkParamDataType(key, value, datatype) { switch (datatype) { case DATA_TYPES.BOOLEAN: if (!utils.isBoolean(value)) { - utils.logWarn(errMsg); + utils.logWarn(LOG_WARN_PREFIX + errMsg); return UNDEFINED; } return value; case DATA_TYPES.NUMBER: if (!utils.isNumber(value)) { - utils.logWarn(errMsg); + utils.logWarn(LOG_WARN_PREFIX + errMsg); return UNDEFINED; } return value; case DATA_TYPES.STRING: if (!utils.isStr(value)) { - utils.logWarn(errMsg); + utils.logWarn(LOG_WARN_PREFIX + errMsg); return UNDEFINED; } return value; case DATA_TYPES.ARRAY: if (!utils.isArray(value)) { - utils.logWarn(errMsg); + utils.logWarn(LOG_WARN_PREFIX + errMsg); return UNDEFINED; } return value; @@ -356,7 +357,7 @@ function _createNativeRequest(params) { } }; } else { - utils.logWarn(BIDDER_CODE + ' Error: Title Length is required for native ad: ' + JSON.stringify(params)); + utils.logWarn(LOG_WARN_PREFIX + 'Error: Title Length is required for native ad: ' + JSON.stringify(params)); } break; case NATIVE_ASSET_KEY.IMAGE: @@ -376,7 +377,7 @@ function _createNativeRequest(params) { }; } else { // Log Warn - utils.logWarn(BIDDER_CODE + ' Error: Image sizes is required for native ad: ' + JSON.stringify(params)); + utils.logWarn(LOG_WARN_PREFIX + 'Error: Image sizes is required for native ad: ' + JSON.stringify(params)); } break; case NATIVE_ASSET_KEY.ICON: @@ -392,7 +393,7 @@ function _createNativeRequest(params) { }; } else { // Log Warn - utils.logWarn(BIDDER_CODE + ' Error: Icon sizes is required for native ad: ' + JSON.stringify(params)); + utils.logWarn(LOG_WARN_PREFIX + 'Error: Icon sizes is required for native ad: ' + JSON.stringify(params)); }; break; case NATIVE_ASSET_KEY.SPONSOREDBY: @@ -650,7 +651,7 @@ function _createImpressionObject(bid, conf) { impObj.banner = bannerObj; } if (isInvalidNativeRequest && impObj.hasOwnProperty('native')) { - utils.logWarn(BIDDER_CODE + ': Call to OpenBid will not be sent for native ad unit as it does not contain required valid native params.' + JSON.stringify(bid) + ' Refer:' + PREBID_NATIVE_HELP_LINK); + utils.logWarn(LOG_WARN_PREFIX + 'Call to OpenBid will not be sent for native ad unit as it does not contain required valid native params.' + JSON.stringify(bid) + ' Refer:' + PREBID_NATIVE_HELP_LINK); return; } return impObj; @@ -717,7 +718,7 @@ function _parseNativeResponse(bid, newBid) { try { adm = JSON.parse(bid.adm.replace(/\\/g, '')); } catch (ex) { - utils.logWarn(BIDDER_CODE + ' Error: Cannot parse native reponse for ad response: ' + newBid.adm); + utils.logWarn(LOG_WARN_PREFIX + 'Error: Cannot parse native reponse for ad response: ' + newBid.adm); return; } if (adm && adm.native && adm.native.assets && adm.native.assets.length > 0) { @@ -784,17 +785,17 @@ export const spec = { isBidRequestValid: bid => { if (bid && bid.params) { if (!utils.isStr(bid.params.publisherId)) { - utils.logWarn(BIDDER_CODE + ' Error: publisherId is mandatory and cannot be numeric. Call to OpenBid will not be sent for ad unit: ' + JSON.stringify(bid)); + utils.logWarn(LOG_WARN_PREFIX + 'Error: publisherId is mandatory and cannot be numeric. Call to OpenBid will not be sent for ad unit: ' + JSON.stringify(bid)); return false; } if (!utils.isStr(bid.params.adSlot)) { - utils.logWarn(BIDDER_CODE + ': adSlotId is mandatory and cannot be numeric. Call to OpenBid will not be sent for ad unit: ' + JSON.stringify(bid)); + utils.logWarn(LOG_WARN_PREFIX + 'Error: adSlotId is mandatory and cannot be numeric. Call to OpenBid will not be sent for ad unit: ' + JSON.stringify(bid)); return false; } // video ad validation if (bid.params.hasOwnProperty('video')) { if (!bid.params.video.hasOwnProperty('mimes') || !utils.isArray(bid.params.video.mimes) || bid.params.video.mimes.length === 0) { - utils.logWarn(BIDDER_CODE + ': For video ads, mimes is mandatory and must specify atlease 1 mime value. Call to OpenBid will not be sent for ad unit:' + JSON.stringify(bid)); + utils.logWarn(LOG_WARN_PREFIX + 'Error: For video ads, mimes is mandatory and must specify atlease 1 mime value. Call to OpenBid will not be sent for ad unit:' + JSON.stringify(bid)); return false; } } @@ -826,12 +827,12 @@ export const spec = { _parseAdSlot(bid); if (bid.params.hasOwnProperty('video')) { if (!(bid.params.adSlot && bid.params.adUnit && bid.params.adUnitIndex)) { - utils.logWarn(BIDDER_CODE + ': Skipping the non-standard adslot: ', bid.params.adSlot, JSON.stringify(bid)); + utils.logWarn(LOG_WARN_PREFIX + 'Skipping the non-standard adslot: ', bid.params.adSlot, JSON.stringify(bid)); return; } } else { if (!(bid.params.adSlot && bid.params.adUnit && bid.params.adUnitIndex && bid.params.width && bid.params.height)) { - utils.logWarn(BIDDER_CODE + ': Skipping the non-standard adslot: ', bid.params.adSlot, JSON.stringify(bid)); + utils.logWarn(LOG_WARN_PREFIX + 'Skipping the non-standard adslot: ', bid.params.adSlot, JSON.stringify(bid)); return; } } @@ -841,7 +842,7 @@ export const spec = { if (bidCurrency === '') { bidCurrency = bid.params.currency || undefined; } else if (bid.params.hasOwnProperty('currency') && bidCurrency !== bid.params.currency) { - utils.logWarn(BIDDER_CODE + ': Currency specifier ignored. Only one currency permitted.'); + utils.logWarn(LOG_WARN_PREFIX + 'Currency specifier ignored. Only one currency permitted.'); } bid.params.currency = bidCurrency; // check if dctr is added to more than 1 adunit @@ -910,13 +911,13 @@ export const spec = { key_val: dctr.trim() } } else { - utils.logWarn(BIDDER_CODE + ': Ignoring param : dctr with value : ' + dctr + ', expects string-value, found empty or non-string value'); + utils.logWarn(LOG_WARN_PREFIX + 'Ignoring param : dctr with value : ' + dctr + ', expects string-value, found empty or non-string value'); } if (dctrArr.length > 1) { - utils.logWarn(BIDDER_CODE + ': dctr value found in more than 1 adunits. Value from 1st adunit will be picked. Ignoring values from subsequent adunits'); + utils.logWarn(LOG_WARN_PREFIX + 'dctr value found in more than 1 adunits. Value from 1st adunit will be picked. Ignoring values from subsequent adunits'); } } else { - utils.logWarn(BIDDER_CODE + ': dctr value not found in 1st adunit, ignoring values from subsequent adunits'); + utils.logWarn(LOG_WARN_PREFIX + 'dctr value not found in 1st adunit, ignoring values from subsequent adunits'); } } @@ -976,6 +977,17 @@ export const spec = { newBid['dealChannel'] = dealChannelValues[bid.ext.deal_channel] || null; } + newBid.meta = {}; + if (bid.ext && bid.ext.dspid) { + newBid.meta.networkId = bid.ext.dspid; + } + if (bid.ext && bid.ext.advid) { + newBid.meta.buyerId = bid.ext.advid; + } + if (bid.adomain && bid.adomain.length > 0) { + newBid.meta.clickUrl = bid.adomain[0]; + } + bidResponses.push(newBid); }); }); @@ -1004,7 +1016,7 @@ export const spec = { url: syncurl }]; } else { - utils.logWarn('PubMatic: Please enable iframe based user sync.'); + utils.logWarn(LOG_WARN_PREFIX + 'Please enable iframe based user sync.'); } }, diff --git a/modules/smartrtbBidAdapter.js b/modules/smartrtbBidAdapter.js new file mode 100644 index 00000000000..561ce58e016 --- /dev/null +++ b/modules/smartrtbBidAdapter.js @@ -0,0 +1,111 @@ +import * as utils from 'src/utils'; +import {registerBidder} from 'src/adapters/bidderFactory'; +const BIDDER_CODE = 'smartrtb'; + +function getDomain () { + if (!utils.inIframe()) { + return window.location.hostname + } + let origins = window.document.location.ancestorOrigins + if (origins && origins.length > 0) { + return origins[origins.length - 1] + } +} + +export const spec = { + code: BIDDER_CODE, + aliases: ['smrtb'], + isBidRequestValid: function(bid) { + return (bid.params.pubId !== null && + bid.params.medId !== null && + bid.params.zoneId !== null); + }, + buildRequests: function(validBidRequests, bidderRequest) { + let stack = (bidderRequest.refererInfo && + bidderRequest.refererInfo.stack ? bidderRequest.refererInfo + : []) + + const payload = { + start_time: utils.timestamp(), + tmax: 120, + language: window.navigator.userLanguage || window.navigator.language, + site: { + domain: getDomain(), + iframe: !bidderRequest.refererInfo.reachedTop, + url: stack && stack.length > 0 ? [stack.length - 1] : null, + https: (window.location.protocol === 'https:'), + referrer: bidderRequest.refererInfo.referer + }, + imps: [] + }; + + if (bidderRequest && bidderRequest.gdprConsent) { + payload.gdpr = { + applies: bidderRequest.gdprConsent.gdprApplies, + consent: bidderRequest.gdprConsent.consentString + }; + } + + for (let x = 0; x < validBidRequests.length; x++) { + let req = validBidRequests[x] + + payload.imps.push({ + pub_id: req.params.pubId, + med_id: req.params.medId, + zone_id: req.params.zoneId, + bid_id: req.bidId, + imp_id: req.transactionId, + sizes: req.sizes, + force_bid: req.params.forceBid + }); + } + + return { + method: 'POST', + url: '//pubs.smrtb.com/json/publisher/prebid', + data: JSON.stringify(payload) + }; + }, + interpretResponse: function(serverResponse, bidRequest) { + const bidResponses = []; + let res = serverResponse.body; + if (!res.bids || !res.bids.length) { + return [] + } + + for (let x = 0; x < serverResponse.body.bids.length; x++) { + let bid = serverResponse.body.bids[x] + + bidResponses.push({ + requestId: bid.bid_id, + cpm: bid.cpm, + width: bid.w, + height: bid.h, + ad: bid.html, + ttl: 120, + creativeId: bid.crid, + netRevenue: true, + currency: 'USD' + }) + } + + return bidResponses; + }, + getUserSyncs: function(syncOptions, serverResponses) { + const syncs = [] + if (syncOptions.iframeEnabled) { + syncs.push({ + type: 'iframe', + url: '//ads.smrtb.com/sync' + }); + } else if (syncOptions.pixelEnabled) { + syncs.push({ + type: 'image', + url: '//ads.smrtb.com/sync' + }); + } + return syncs; + } +}; + +registerBidder(spec); diff --git a/modules/smartrtbBidAdapter.md b/modules/smartrtbBidAdapter.md new file mode 100644 index 00000000000..f043e277d0d --- /dev/null +++ b/modules/smartrtbBidAdapter.md @@ -0,0 +1,34 @@ +# Overview + +``` +Module Name: Smart RTB (smrtb.com) Bidder Adapter +Module Type: Bidder Adapter +Maintainer: evanm@smrtb.com +``` + +# Description + +Prebid adapter for Smart RTB. Requires approval and account setup. + +# Test Parameters + +## Web +``` + var adUnits = [ + { + code: 'test-div', + sizes: [[300, 250]], + bids: [ + { + bidder: "smartrtb", + params: { + pubId: 123, + medId: "m_00a95d003340dbb2fcb8ee668a84fa", + zoneId: "z_261b6c7e7d4d4985393b293cc903d1", + force_bid: true + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/modules/smilewantedBidAdapter.js b/modules/smilewantedBidAdapter.js new file mode 100644 index 00000000000..bf9a75b6650 --- /dev/null +++ b/modules/smilewantedBidAdapter.js @@ -0,0 +1,113 @@ +import * as utils from '../src/utils'; +import { config } from '../src/config'; +import { registerBidder } from '../src/adapters/bidderFactory'; + +export const spec = { + code: 'smilewanted', + aliases: ['smile', 'sw'], + + /** + * 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 && bid.params.zoneId); + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} validBidRequests A non-empty list of valid bid requests that should be sent to the Server. + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function(validBidRequests, bidderRequest) { + return validBidRequests.map(bid => { + var payload = { + zoneId: bid.params.zoneId, + currencyCode: config.getConfig('currency.adServerCurrency') || 'EUR', + bidfloor: bid.params.bidfloor || 0.0, + tagId: bid.adUnitCode, + sizes: bid.sizes.map(size => ({ + w: size[0], + h: size[1] + })), + transactionId: bid.transactionId, + timeout: config.getConfig('bidderTimeout'), + bidId: bid.bidId, + prebidVersion: '$prebid.version$' + }; + + if (bidderRequest && bidderRequest.refererInfo) { + payload.pageDomain = bidderRequest.refererInfo.referer || ''; + } + + if (bidderRequest && bidderRequest.gdprConsent) { + payload.gdpr_consent = bidderRequest.gdprConsent.consentString; + payload.gdpr = bidderRequest.gdprConsent.gdprApplies; // we're handling the undefined case server side + } + var payloadString = JSON.stringify(payload); + return { + method: 'POST', + url: 'https://prebid.smilewanted.com', + 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, bidRequest) { + const bidResponses = []; + var response = serverResponse.body; + try { + if (response) { + const bidResponse = { + requestId: JSON.parse(bidRequest.data).bidId, + cpm: response.cpm, + width: response.width, + height: response.height, + creativeId: response.creativeId, + dealId: response.dealId, + currency: response.currency, + netRevenue: response.isNetCpm, + ttl: response.ttl, + adUrl: response.adUrl, + ad: response.ad + }; + + bidResponses.push(bidResponse); + } + } catch (error) { + utils.logError('Error while parsing smilewanted response', error); + } + return bidResponses; + }, + + /** + * User syncs. + * + * @param {*} syncOptions Publisher prebid configuration. + * @param {*} serverResponses A successful response from the server. + * @return {Syncs[]} An array of syncs that should be executed. + */ + getUserSyncs: function(syncOptions, serverResponses) { + const syncs = [] + if (syncOptions.iframeEnabled && serverResponses.length > 0) { + if (serverResponses[0].body.cSyncUrl === 'https://csync.smilewanted.com') { + syncs.push({ + type: 'iframe', + url: serverResponses[0].body.cSyncUrl + }); + } + } + return syncs; + } +} + +registerBidder(spec); diff --git a/modules/smilewantedBidAdapter.md b/modules/smilewantedBidAdapter.md new file mode 100644 index 00000000000..ddc25fe7456 --- /dev/null +++ b/modules/smilewantedBidAdapter.md @@ -0,0 +1,31 @@ +# Overview + +``` +Module Name: SmileWanted Bidder Adapter +Module Type: Bidder Adapter +Maintainer: maxime@smilewanted.com +``` + +# Description + +To use us as a bidder you must have an account and an active "zoneId" on our SmileWanted platform. + +# Test Parameters + +## Web +``` + var adUnits = [ + { + code: 'test-div', + sizes: [[300, 250]], + bids: [ + { + bidder: "smilewanted", + params: { + zoneId: 1 + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/modules/sonobiAnalyticsAdapter.js b/modules/sonobiAnalyticsAdapter.js new file mode 100644 index 00000000000..ab110cb4d01 --- /dev/null +++ b/modules/sonobiAnalyticsAdapter.js @@ -0,0 +1,277 @@ +import adapter from '../src/AnalyticsAdapter'; +import CONSTANTS from '../src/constants.json'; +import adapterManager from '../src/adapterManager'; +import {ajaxBuilder} from '../src/ajax'; + +const utils = require('../src/utils'); +let ajax = ajaxBuilder(0); + +const DEFAULT_EVENT_URL = 'apex.go.sonobi.com/keymaker'; +const analyticsType = 'endpoint'; +const QUEUE_TIMEOUT_DEFAULT = 200; +const { + EVENTS: { + AUCTION_INIT, + AUCTION_END, + BID_REQUESTED, + BID_ADJUSTMENT, + BIDDER_DONE, + BID_WON, + BID_RESPONSE, + BID_TIMEOUT + } +} = CONSTANTS; + +let initOptions = {}; +let auctionCache = {}; +let auctionTtl = 60 * 60 * 1000; + +function deleteOldAuctions() { + for (let auctionId in auctionCache) { + let auction = auctionCache[auctionId]; + if (Date.now() - auction.start > auctionTtl) { + delete auctionCache[auctionId]; + } + } +} + +function buildAuctionEntity(args) { + return { + 'id': args.auctionId, + 'start': args.timestamp, + 'timeout': args.timeout, + 'adUnits': {}, + 'stats': {}, + 'queue': [], + 'qTimeout': false + }; +} +function buildAdUnit(data) { + return `/${initOptions.pubId}/${initOptions.siteId}/${data.adUnitCode.toLowerCase()}`; +} +function getLatency(data) { + if (!data.responseTimestamp) { + return -1; + } else { + return data.responseTimestamp - data.requestTimestamp; + } +} +function getBid(data) { + if (data.cpm) { + return Math.round(data.cpm * 100); + } else { + return 0; + } +} +function buildItem(data, response, phase = 1) { + let size = data.width ? {width: data.width, height: data.height} : {width: data.sizes[0][0], height: data.sizes[0][1]}; + return { + 'bidid': data.bidId || data.requestId, + 'p': phase, + 'buyerid': data.bidder.toLowerCase(), + 'bid': getBid(data), + 'adunit_code': buildAdUnit(data), + 's': `${size.width}x${size.height}`, + 'latency': getLatency(data), + 'response': response, + 'jsLatency': getLatency(data), + 'buyername': data.bidder.toLowerCase() + }; +} +function sendQueue(auctionId) { + let auction = auctionCache[auctionId]; + let data = auction.queue; + auction.queue = []; + auction.qTimeout = false; + sonobiAdapter.sendData(auction, data); +} +function addToAuctionQueue(auctionId, id) { + let auction = auctionCache[auctionId]; + auction.queue = auction.queue.filter((item) => { + if (item.bidid !== id) { return true; } + return auction.stats[id].data.p !== item.p; + }); + auction.queue.push(utils.deepClone(auction.stats[id].data)); + if (!auction.qTimeout) { + auction.qTimeout = setTimeout(() => { + sendQueue(auctionId); + }, initOptions.delay) + } +} +function updateBidStats(auctionId, id, data) { + let auction = auctionCache[auctionId]; + auction.stats[id].data = {...auction.stats[id].data, ...data}; + addToAuctionQueue(auctionId, id); + logInfo('Updated Bid Stats: ', auction.stats[id]); + return auction.stats[id]; +} + +function handleOtherEvents(eventType, args) { + logInfo('Other Event: ' + eventType, args); +} + +function handlerAuctionInit(args) { + auctionCache[args.auctionId] = buildAuctionEntity(args); + deleteOldAuctions(); + logInfo('Auction Init', args); +} +function handlerBidRequested(args) { + let auction = auctionCache[args.auctionId]; + let data = []; + let phase = 1; + let response = 1; + args.bids.forEach(function (bidRequest) { + auction = auctionCache[bidRequest.auctionId] + let built = buildItem(bidRequest, response, phase); + auction.stats[built.bidid] = {id: built.bidid, adUnitCode: bidRequest.adUnitCode, data: built}; + addToAuctionQueue(args.auctionId, built.bidid); + }) + + logInfo('Bids Requested ', data); +} + +function handlerBidAdjustment(args) { + logInfo('Bid Adjustment', args); +} +function handlerBidderDone(args) { + logInfo('Bidder Done', args); +} + +function handlerAuctionEnd(args) { + let winners = {}; + args.bidsReceived.forEach((bid) => { + if (!winners[bid.adUnitCode]) { + winners[bid.adUnitCode] = {bidId: bid.requestId, cpm: bid.cpm}; + } else if (winners[bid.adUnitCode].cpm < bid.cpm) { + winners[bid.adUnitCode] = {bidId: bid.requestId, cpm: bid.cpm}; + } + }) + args.adUnitCodes.forEach((adUnitCode) => { + if (winners[adUnitCode]) { + let bidId = winners[adUnitCode].bidId; + updateBidStats(args.auctionId, bidId, {response: 4}); + } + }) + logInfo('Auction End', args); + logInfo('Auction Cache', auctionCache[args.auctionId].stats); +} +function handlerBidWon(args) { + let {auctionId, requestId} = args; + let res = updateBidStats(auctionId, requestId, {p: 3, response: 6}); + logInfo('Bid Won ', args); + logInfo('Bid Update Result: ', res); +} +function handlerBidResponse(args) { + let {auctionId, requestId, cpm, size, timeToRespond} = args; + updateBidStats(auctionId, requestId, {bid: cpm, s: size, jsLatency: timeToRespond, latency: timeToRespond, p: 2, response: 9}); + + logInfo('Bid Response ', args); +} +function handlerBidTimeout(args) { + let {auctionId, bidId} = args; + logInfo('Bid Timeout ', args); + updateBidStats(auctionId, bidId, {p: 2, response: 0, latency: args.timeout, jsLatency: args.timeout}); +} +let sonobiAdapter = Object.assign(adapter({url: DEFAULT_EVENT_URL, analyticsType}), { + track({eventType, args}) { + switch (eventType) { + case AUCTION_INIT: + handlerAuctionInit(args); + break; + case BID_REQUESTED: + handlerBidRequested(args); + break; + case BID_ADJUSTMENT: + handlerBidAdjustment(args); + break; + case BIDDER_DONE: + handlerBidderDone(args); + break; + case AUCTION_END: + handlerAuctionEnd(args); + break; + case BID_WON: + handlerBidWon(args); + break; + case BID_RESPONSE: + handlerBidResponse(args); + break; + case BID_TIMEOUT: + handlerBidTimeout(args); + break; + default: + handleOtherEvents(eventType, args); + break; + } + }, + +}); + +sonobiAdapter.originEnableAnalytics = sonobiAdapter.enableAnalytics; + +sonobiAdapter.enableAnalytics = function (config) { + if (this.initConfig(config)) { + logInfo('Analytics adapter enabled', initOptions); + sonobiAdapter.originEnableAnalytics(config); + } +}; + +sonobiAdapter.initConfig = function (config) { + let isCorrectConfig = true; + initOptions = {}; + initOptions.options = utils.deepClone(config.options); + + initOptions.pubId = initOptions.options.pubId || null; + initOptions.siteId = initOptions.options.siteId || null; + initOptions.delay = initOptions.options.delay || QUEUE_TIMEOUT_DEFAULT; + if (!initOptions.pubId) { + logError('"options.pubId" is empty'); + isCorrectConfig = false; + } + if (!initOptions.siteId) { + logError('"options.siteId" is empty'); + isCorrectConfig = false; + } + + initOptions.server = DEFAULT_EVENT_URL; + initOptions.host = initOptions.options.host || window.location.hostname; + this.initOptions = initOptions; + return isCorrectConfig; +}; + +sonobiAdapter.getOptions = function () { + return initOptions; +}; + +sonobiAdapter.sendData = function (auction, data) { + let url = 'https://' + initOptions.server + '?pageviewid=' + auction.id + '&corscred=1&pubId=' + initOptions.pubId + '&siteId=' + initOptions.siteId; + ajax( + url, + function () { logInfo('Auction [' + auction.id + '] sent ', data); }, + JSON.stringify(data), + { + method: 'POST', + // withCredentials: true, + contentType: 'text/plain' + } + ); +} + +function logInfo(message, meta) { + utils.logInfo(buildLogMessage(message), meta); +} + +function logError(message) { + utils.logError(buildLogMessage(message)); +} + +function buildLogMessage(message) { + return 'Sonobi Prebid Analytics: ' + message; +} + +adapterManager.registerAnalyticsAdapter({ + adapter: sonobiAdapter, + code: 'sonobi' +}); + +export default sonobiAdapter; diff --git a/modules/sonobiAnalyticsAdapter.md b/modules/sonobiAnalyticsAdapter.md new file mode 100644 index 00000000000..1ef46bac833 --- /dev/null +++ b/modules/sonobiAnalyticsAdapter.md @@ -0,0 +1,24 @@ +# Overview + +``` +Module Name: Sonobi Analytics Adapter +Module Type: Analytics Adapter +Maintainer: apex@sonobi.com +``` + +# Description + +Module that connects to Sonobi's Analytics service + +# Test Parameters +``` + + pbjs.enableAnalytics({ + provider: 'sonobi', + options: { + pubId: 'ffBB352', + siteId: 57463, + delay: 300 + } + }); +``` diff --git a/modules/trustxBidAdapter.js b/modules/trustxBidAdapter.js index bd5f63e5302..b0593e5c7d7 100644 --- a/modules/trustxBidAdapter.js +++ b/modules/trustxBidAdapter.js @@ -1,9 +1,14 @@ import * as utils from '../src/utils'; import {registerBidder} from '../src/adapters/bidderFactory'; +import { Renderer } from '../src/Renderer'; +import { VIDEO, BANNER } from '../src/mediaTypes'; + const BIDDER_CODE = 'trustx'; const ENDPOINT_URL = '//sofia.trustx.org/hb'; const TIME_TO_LIVE = 360; const ADAPTER_SYNC_URL = '//sofia.trustx.org/push_sync'; +const RENDERER_URL = '//cdn.adnxs.com/renderer/video/ANOutstreamVideo.js'; + const LOG_ERROR_MESS = { noAuid: 'Bid from response has no auid parameter - ', noAdm: 'Bid from response has no adm parameter - ', @@ -17,6 +22,7 @@ const LOG_ERROR_MESS = { }; export const spec = { code: BIDDER_CODE, + supportedMediaTypes: [ BANNER, VIDEO ], /** * Determines whether or not the given bid request is valid. * @@ -113,7 +119,7 @@ export const spec = { * @param {*} bidRequest * @return {Bid[]} An array of bids which were nested inside the server. */ - interpretResponse: function(serverResponse, bidRequest) { + interpretResponse: function(serverResponse, bidRequest, RendererConst = Renderer) { serverResponse = serverResponse && serverResponse.body; const bidResponses = []; const bidsMap = bidRequest.bidsMap; @@ -128,7 +134,7 @@ export const spec = { if (!errorMessage && serverResponse.seatbid) { serverResponse.seatbid.forEach(respItem => { - _addBidResponse(_getBidFromResponse(respItem), bidsMap, priceType, bidResponses); + _addBidResponse(_getBidFromResponse(respItem), bidsMap, priceType, bidResponses, RendererConst); }); } if (errorMessage) utils.logError(errorMessage); @@ -155,7 +161,7 @@ function _getBidFromResponse(respItem) { return respItem && respItem.bid && respItem.bid[0]; } -function _addBidResponse(serverBid, bidsMap, priceType, bidResponses) { +function _addBidResponse(serverBid, bidsMap, priceType, bidResponses, RendererConst) { if (!serverBid) return; let errorMessage; if (!serverBid.auid) errorMessage = LOG_ERROR_MESS.noAuid + JSON.stringify(serverBid); @@ -168,7 +174,7 @@ function _addBidResponse(serverBid, bidsMap, priceType, bidResponses) { const slot = awaitingBids[sizeId][0]; const bid = slot.bids.shift(); - bidResponses.push({ + const bidResponse = { requestId: bid.bidId, // bid.bidderRequestId, bidderCode: spec.code, cpm: serverBid.price, @@ -178,9 +184,26 @@ function _addBidResponse(serverBid, bidsMap, priceType, bidResponses) { currency: 'USD', netRevenue: priceType !== 'gross', ttl: TIME_TO_LIVE, - ad: serverBid.adm, dealId: serverBid.dealid - }); + }; + if (serverBid.content_type === 'video') { + bidResponse.vastXml = serverBid.adm; + bidResponse.mediaType = VIDEO; + bidResponse.adResponse = { + content: bidResponse.vastXml + }; + if (!bid.renderer && (!bid.mediaTypes || !bid.mediaTypes.video || bid.mediaTypes.video.context === 'outstream')) { + bidResponse.renderer = createRenderer(bidResponse, { + id: bid.bidId, + url: RENDERER_URL + }, RendererConst); + } + } else { + bidResponse.ad = serverBid.adm; + bidResponse.mediaType = BANNER; + } + + bidResponses.push(bidResponse); if (!slot.bids.length) { slot.parents.forEach(({parent, key, uid}) => { @@ -206,4 +229,29 @@ function _addBidResponse(serverBid, bidsMap, priceType, bidResponses) { } } +function outstreamRender (bid) { + bid.renderer.push(() => { + window.ANOutstreamVideo.renderAd({ + targetId: bid.adUnitCode, + adResponse: bid.adResponse + }); + }); +} + +function createRenderer (bid, rendererParams, RendererConst) { + const rendererInst = RendererConst.install({ + id: rendererParams.id, + url: rendererParams.url, + loaded: false + }); + + try { + rendererInst.setRender(outstreamRender); + } catch (err) { + utils.logWarn('Prebid Error calling setRender on renderer', err); + } + + return rendererInst; +} + registerBidder(spec); diff --git a/modules/trustxBidAdapter.md b/modules/trustxBidAdapter.md index ca407b0c5e8..d6b660c6248 100755 --- a/modules/trustxBidAdapter.md +++ b/modules/trustxBidAdapter.md @@ -7,6 +7,7 @@ Maintainer: paul@trustx.org # Description Module that connects to TrustX demand source to fetch bids. +TrustX Bid Adapter supports Banner and Video (instream and outstream). # Test Parameters ``` @@ -35,6 +36,18 @@ Module that connects to TrustX demand source to fetch bids. } } ] + },{ + code: 'test-div', + sizes: [[640, 360]], + mediaTypes: { video: {} }, + bids: [ + { + bidder: "trustx", + params: { + uid: 7697 + } + } + ] } ]; ``` \ No newline at end of file diff --git a/modules/yieldoneBidAdapter.js b/modules/yieldoneBidAdapter.js index 728665bb204..7abb94736df 100644 --- a/modules/yieldoneBidAdapter.js +++ b/modules/yieldoneBidAdapter.js @@ -21,12 +21,16 @@ export const spec = { const cb = Math.floor(Math.random() * 99999999999); const referrer = encodeURIComponent(utils.getTopWindowUrl()); const bidId = bidRequest.bidId; + const unitCode = bidRequest.adUnitCode; + const timeout = config.getConfig('bidderTimeout'); const payload = { v: 'hb1', p: placementId, cb: cb, r: referrer, uid: bidId, + uc: unitCode, + tmax: timeout, t: 'i' }; diff --git a/package.json b/package.json index cd78a3036b9..d73d9014d99 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "2.6.0-pre", + "version": "2.8.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { diff --git a/src/sizeMapping.js b/src/sizeMapping.js index b8278df69c0..04c9773a1af 100644 --- a/src/sizeMapping.js +++ b/src/sizeMapping.js @@ -1,5 +1,5 @@ import { config } from './config'; -import {logWarn, isPlainObject, deepAccess, deepClone} from './utils'; +import {logWarn, isPlainObject, deepAccess, deepClone, getWindowTop} from './utils'; import includes from 'core-js/library/fn/array/includes'; let sizeConfig = []; @@ -123,7 +123,17 @@ function evaluateSizeConfig(configs) { typeof config === 'object' && typeof config.mediaQuery === 'string' ) { - if (matchMedia(config.mediaQuery).matches) { + let ruleMatch = false; + + try { + ruleMatch = getWindowTop().matchMedia(config.mediaQuery).matches; + } catch (e) { + logWarn('Unfriendly iFrame blocks sizeConfig from being correctly evaluated'); + + ruleMatch = matchMedia(config.mediaQuery).matches; + } + + if (ruleMatch) { if (Array.isArray(config.sizesSupported)) { results.shouldFilter = true; } diff --git a/src/utils.js b/src/utils.js index 5e70b31b0b2..a93e25356bd 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1276,3 +1276,20 @@ export function getMinValueFromArray(array) { export function getMaxValueFromArray(array) { return Math.max(...array); } + +/** + * This function will create compare function to sort on object property + * @param {string} property + * @returns {function} compare function to be used in sorting + */ +export function compareOn(property) { + return function compare(a, b) { + if (a[property] < b[property]) { + return 1; + } + if (a[property] > b[property]) { + return -1; + } + return 0; + } +} diff --git a/test/spec/auctionmanager_spec.js b/test/spec/auctionmanager_spec.js index 9110e1fcdc0..aab1da11653 100644 --- a/test/spec/auctionmanager_spec.js +++ b/test/spec/auctionmanager_spec.js @@ -139,7 +139,10 @@ describe('auctionmanager.js', function () { expected[ CONSTANTS.TARGETING_KEYS.SIZE ] = bid.getSize(); expected[ CONSTANTS.TARGETING_KEYS.SOURCE ] = bid.source; expected[ CONSTANTS.TARGETING_KEYS.FORMAT ] = bid.mediaType; - + if (bid.mediaType === 'video') { + expected[ CONSTANTS.TARGETING_KEYS.UUID ] = bid.videoCacheKey; + expected[ CONSTANTS.TARGETING_KEYS.CACHE_ID ] = bid.videoCacheKey; + } if (!keys) { return expected; } @@ -157,8 +160,20 @@ describe('auctionmanager.js', function () { }); it('No bidder level configuration defined - default', function () { - var expected = getDefaultExpected(bid); - var response = getKeyValueTargetingPairs(bid.bidderCode, bid, CONSTANTS.GRANULARITY_OPTIONS.MEDIUM); + $$PREBID_GLOBAL$$.bidderSettings = {}; + let expected = getDefaultExpected(bid); + let response = getKeyValueTargetingPairs(bid.bidderCode, bid); + assert.deepEqual(response, expected); + }); + + it('No bidder level configuration defined - default for video', function () { + $$PREBID_GLOBAL$$.bidderSettings = {}; + let videoBid = utils.deepClone(bid); + videoBid.mediaType = 'video'; + videoBid.videoCacheKey = 'abc123def'; + + let expected = getDefaultExpected(videoBid); + let response = getKeyValueTargetingPairs(videoBid.bidderCode, videoBid); assert.deepEqual(response, expected); }); @@ -213,6 +228,70 @@ describe('auctionmanager.js', function () { assert.deepEqual(response, expected); }); + it('Custom configuration for all bidders with video bid', function () { + let videoBid = utils.deepClone(bid); + videoBid.mediaType = 'video'; + videoBid.videoCacheKey = 'abc123def'; + + $$PREBID_GLOBAL$$.bidderSettings = + { + standard: { + adserverTargeting: [ + { + key: CONSTANTS.TARGETING_KEYS.BIDDER, + val: function (bidResponse) { + return bidResponse.bidderCode; + } + }, { + key: CONSTANTS.TARGETING_KEYS.AD_ID, + val: function (bidResponse) { + return bidResponse.adId; + } + }, { + key: CONSTANTS.TARGETING_KEYS.PRICE_BUCKET, + val: function (bidResponse) { + return bidResponse.pbMg; + } + }, { + key: CONSTANTS.TARGETING_KEYS.SIZE, + val: function (bidResponse) { + return bidResponse.size; + } + }, + { + key: CONSTANTS.TARGETING_KEYS.SOURCE, + val: function (bidResponse) { + return bidResponse.source; + } + }, + { + key: CONSTANTS.TARGETING_KEYS.FORMAT, + val: function (bidResponse) { + return bidResponse.mediaType; + } + }, + { + key: CONSTANTS.TARGETING_KEYS.UUID, + val: function (bidResponse) { + return bidResponse.videoCacheKey; + } + }, + { + key: CONSTANTS.TARGETING_KEYS.CACHE_ID, + val: function (bidResponse) { + return bidResponse.videoCacheKey; + } + } + ] + + } + }; + + let expected = getDefaultExpected(videoBid); + let response = getKeyValueTargetingPairs(videoBid.bidderCode, videoBid); + assert.deepEqual(response, expected); + }); + it('Custom configuration for one bidder', function () { $$PREBID_GLOBAL$$.bidderSettings = { diff --git a/test/spec/modules/adgenerationBidAdapter_spec.js b/test/spec/modules/adgenerationBidAdapter_spec.js index a2c8125850a..28bccd7844e 100644 --- a/test/spec/modules/adgenerationBidAdapter_spec.js +++ b/test/spec/modules/adgenerationBidAdapter_spec.js @@ -238,7 +238,7 @@ describe('AdgenerationAdapter', function () { { data: { label: 'optout_url', - value: 'https://supership.jp/optout/' + value: 'https://supership.jp/optout/#' }, id: 502 }, @@ -350,6 +350,7 @@ describe('AdgenerationAdapter', function () { sponsoredBy: 'Sponsored', body: 'Description', cta: 'CTA', + privacyLink: 'https://supership.jp/optout/#', 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'] @@ -396,6 +397,7 @@ describe('AdgenerationAdapter', function () { expect(result.native.sponsoredBy).to.equal(bidResponses.native.native.sponsoredBy); expect(result.native.body).to.equal(bidResponses.native.native.body); expect(result.native.cta).to.equal(bidResponses.native.native.cta); + expect(decodeURIComponent(result.native.privacyLink)).to.equal(bidResponses.native.native.privacyLink); expect(result.native.clickUrl).to.equal(bidResponses.native.native.clickUrl); expect(result.native.impressionTrackers[0]).to.equal(bidResponses.native.native.impressionTrackers[0]); expect(result.native.clickTrackers[0]).to.equal(bidResponses.native.native.clickTrackers[0]); diff --git a/test/spec/modules/adpod_spec.js b/test/spec/modules/adpod_spec.js index 16e94fd569f..a47c77d25ac 100644 --- a/test/spec/modules/adpod_spec.js +++ b/test/spec/modules/adpod_spec.js @@ -4,7 +4,7 @@ import * as videoCache from 'src/videoCache'; import * as auction from 'src/auction'; import { ADPOD } from 'src/mediaTypes'; -import { callPrebidCacheHook, checkAdUnitSetupHook, checkVideoBidSetupHook, adpodSetConfig } from 'modules/adpod'; +import { callPrebidCacheHook, checkAdUnitSetupHook, checkVideoBidSetupHook, adpodSetConfig, sortByPricePerSecond } from 'modules/adpod'; let expect = require('chai').expect; @@ -150,11 +150,13 @@ describe('adpod.js', function () { expect(auctionBids[0].customCacheKey).to.exist.and.to.match(/^5\.00_test_15s_.*/); expect(auctionBids[0].adserverTargeting.hb_pb_cat_dur).to.equal('5.00_test_15s'); expect(auctionBids[0].adserverTargeting.hb_cache_id).to.exist; + expect(auctionBids[0].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id) expect(auctionBids[1].adId).to.equal(bidResponse2.adId); expect(auctionBids[1].customCacheKey).to.exist.and.to.match(/^12\.00_value_15s_.*/); expect(auctionBids[1].adserverTargeting.hb_pb_cat_dur).to.equal('12.00_value_15s'); expect(auctionBids[1].adserverTargeting.hb_cache_id).to.exist; expect(auctionBids[1].adserverTargeting.hb_cache_id).to.equal(auctionBids[0].adserverTargeting.hb_cache_id); + expect(auctionBids[1].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id); }); it('should send prebid cache call once bid queue is full', function () { @@ -220,10 +222,12 @@ describe('adpod.js', function () { expect(auctionBids[0].customCacheKey).to.exist.and.to.match(/^10\.00_airline_30s_.*/); expect(auctionBids[0].adserverTargeting.hb_pb_cat_dur).to.equal('10.00_airline_30s'); expect(auctionBids[0].adserverTargeting.hb_cache_id).to.exist; + expect(auctionBids[0].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id) expect(auctionBids[1].adId).to.equal('adId234'); expect(auctionBids[1].customCacheKey).to.exist.and.to.match(/^15\.00_airline_30s_.*/); expect(auctionBids[1].adserverTargeting.hb_pb_cat_dur).to.equal('15.00_airline_30s'); expect(auctionBids[1].adserverTargeting.hb_cache_id).to.exist; + expect(auctionBids[1].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id) }); it('should send prebid cache call after set period of time (even if queue is not full)', function () { @@ -276,6 +280,7 @@ describe('adpod.js', function () { expect(auctionBids[0].customCacheKey).to.exist.and.to.match(/^15\.00_airline_30s_.*/); expect(auctionBids[0].adserverTargeting.hb_pb_cat_dur).to.equal('15.00_airline_30s'); expect(auctionBids[0].adserverTargeting.hb_cache_id).to.exist; + expect(auctionBids[0].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id) }); it('should execute multiple prebid cache calls when number of bids exceeds queue size', function () { @@ -360,14 +365,17 @@ describe('adpod.js', function () { expect(auctionBids[0].customCacheKey).to.exist.and.to.match(/^15\.00_airline_15s_.*/); expect(auctionBids[0].adserverTargeting.hb_pb_cat_dur).to.equal('15.00_airline_15s'); expect(auctionBids[0].adserverTargeting.hb_cache_id).to.exist; + expect(auctionBids[0].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id) expect(auctionBids[1].adId).to.equal('multi_ad2'); expect(auctionBids[1].customCacheKey).to.exist.and.to.match(/^15\.00_news_15s_.*/); expect(auctionBids[1].adserverTargeting.hb_pb_cat_dur).to.equal('15.00_news_15s'); expect(auctionBids[1].adserverTargeting.hb_cache_id).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id); + expect(auctionBids[1].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id) expect(auctionBids[2].adId).to.equal('multi_ad3'); expect(auctionBids[2].customCacheKey).to.exist.and.to.match(/^10\.00_sports_15s_.*/); expect(auctionBids[2].adserverTargeting.hb_pb_cat_dur).to.equal('10.00_sports_15s'); expect(auctionBids[2].adserverTargeting.hb_cache_id).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id); + expect(auctionBids[2].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id) }); it('should cache the bids with a shortened custom key when adpod.brandCategoryExclusion is false', function() { @@ -436,10 +444,12 @@ describe('adpod.js', function () { expect(auctionBids[0].customCacheKey).to.exist.and.to.match(/^10\.00_15s_.*/); expect(auctionBids[0].adserverTargeting.hb_pb_cat_dur).to.equal('10.00_15s'); expect(auctionBids[0].adserverTargeting.hb_cache_id).to.exist; + expect(auctionBids[0].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id) expect(auctionBids[1].adId).to.equal('nocat_ad2'); expect(auctionBids[1].customCacheKey).to.exist.and.to.match(/^15\.00_15s_.*/); expect(auctionBids[1].adserverTargeting.hb_pb_cat_dur).to.equal('15.00_15s'); expect(auctionBids[1].adserverTargeting.hb_cache_id).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id); + expect(auctionBids[1].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id) }); it('should not add bid to auction when config adpod.brandCategoryExclusion is true but bid is missing adServerCatId', function() { @@ -564,6 +574,7 @@ describe('adpod.js', function () { expect(auctionBids[0].customCacheKey).to.exist.and.to.match(/^5\.00_tech_45s_.*/); expect(auctionBids[0].adserverTargeting.hb_pb_cat_dur).to.equal('5.00_tech_45s'); expect(auctionBids[0].adserverTargeting.hb_cache_id).to.exist; + expect(auctionBids[0].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id) }); it('should not add bids to auction if PBC returns an error', function() { @@ -979,4 +990,63 @@ describe('adpod.js', function () { expect(logWarnStub.called).to.equal(false); }) }); + + describe('adpod utils', function() { + it('should sort bids array', function() { + let bids = [{ + cpm: 10.12345, + video: { + durationBucket: 15 + } + }, { + cpm: 15, + video: { + durationBucket: 15 + } + }, { + cpm: 15.00, + video: { + durationBucket: 30 + } + }, { + cpm: 5.45, + video: { + durationBucket: 5 + } + }, { + cpm: 20.1234567, + video: { + durationBucket: 60 + } + }] + bids.sort(sortByPricePerSecond); + let sortedBids = [{ + cpm: 5.45, + video: { + durationBucket: 5 + } + }, { + cpm: 15, + video: { + durationBucket: 15 + } + }, { + cpm: 10.12345, + video: { + durationBucket: 15 + } + }, { + cpm: 15.00, + video: { + durationBucket: 30 + } + }, { + cpm: 20.1234567, + video: { + durationBucket: 60 + } + }] + expect(bids).to.include.deep.ordered.members(sortedBids); + }); + }) }); diff --git a/test/spec/modules/advangelistsBidAdapter_spec.js b/test/spec/modules/advangelistsBidAdapter_spec.js old mode 100644 new mode 100755 index f7a49ef995f..fbdfc9f30ee --- a/test/spec/modules/advangelistsBidAdapter_spec.js +++ b/test/spec/modules/advangelistsBidAdapter_spec.js @@ -7,9 +7,9 @@ describe('advangelistsBidAdapter', function () { let bidRequestsVid; beforeEach(function () { - bidRequests = [{'bidder': 'avng', 'params': {'pubid': '0cf8d6d643e13d86a5b6374148a4afac', 'placement': 1234}, 'crumbs': {'pubcid': '979fde13-c71e-4ac2-98b7-28c90f99b449'}, 'mediaTypes': {'banner': {'sizes': [[300, 250]]}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': 'f72931e6-2b0e-4e37-a2bc-1ea912141f81', 'sizes': [[300, 250]], 'bidId': '2aa73f571eaf29', 'bidderRequestId': '1bac84515a7af3', 'auctionId': '5dbc60fa-1aa1-41ce-9092-e6bbd4d478f7', 'src': 'client', 'bidRequestsCount': 1, 'pageurl': 'http://google.com'}]; + bidRequests = [{'bidder': 'advangelists', 'params': {'pubid': '0cf8d6d643e13d86a5b6374148a4afac', 'placement': 1234}, 'crumbs': {'pubcid': '979fde13-c71e-4ac2-98b7-28c90f99b449'}, 'mediaTypes': {'banner': {'sizes': [[300, 250]]}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': 'f72931e6-2b0e-4e37-a2bc-1ea912141f81', 'sizes': [[300, 250]], 'bidId': '2aa73f571eaf29', 'bidderRequestId': '1bac84515a7af3', 'auctionId': '5dbc60fa-1aa1-41ce-9092-e6bbd4d478f7', 'src': 'client', 'bidRequestsCount': 1, 'pageurl': 'http://google.com'}]; - bidRequestsVid = [{'bidder': 'avng', 'params': {'pubid': '8537f00948fc37cc03c5f0f88e198a76', 'placement': 1234, 'video': {'id': 123, 'skip': 1, 'mimes': ['video/mp4', 'application/javascript'], 'playbackmethod': [2, 6], 'maxduration': 30}}, 'crumbs': {'pubcid': '979fde13-c71e-4ac2-98b7-28c90f99b449'}, 'mediaTypes': {'video': {'playerSize': [[320, 480]], 'context': 'instream'}}, 'adUnitCode': 'video1', 'transactionId': '8b060952-93f7-4863-af44-bb8796b97c42', 'sizes': [], 'bidId': '25c6ab92aa0e81', 'bidderRequestId': '1d420b73a013fc', 'auctionId': '9a69741c-34fb-474c-83e1-cfa003aaee17', 'src': 'client', 'bidRequestsCount': 1, 'pageurl': 'http://google.com'}]; + bidRequestsVid = [{'bidder': 'advangelists', 'params': {'pubid': '8537f00948fc37cc03c5f0f88e198a76', 'placement': 1234, 'video': {'id': 123, 'skip': 1, 'mimes': ['video/mp4', 'application/javascript'], 'playbackmethod': [2, 6], 'maxduration': 30}}, 'crumbs': {'pubcid': '979fde13-c71e-4ac2-98b7-28c90f99b449'}, 'mediaTypes': {'video': {'playerSize': [[320, 480]], 'context': 'instream'}}, 'adUnitCode': 'video1', 'transactionId': '8b060952-93f7-4863-af44-bb8796b97c42', 'sizes': [], 'bidId': '25c6ab92aa0e81', 'bidderRequestId': '1d420b73a013fc', 'auctionId': '9a69741c-34fb-474c-83e1-cfa003aaee17', 'src': 'client', 'bidRequestsCount': 1, 'pageurl': 'http://google.com'}]; }); describe('spec.isBidRequestValid', function () { @@ -86,14 +86,14 @@ describe('advangelistsBidAdapter', function () { it('should return valid video bid responses', function () { let _mediaTypes = VIDEO; - const avngbidreqVid = {'bidRequest': {'mediaTypes': {'video': {'w': 320, 'h': 480}}}}; + const advangelistsbidreqVid = {'bidRequest': {'mediaTypes': {'video': {'w': 320, 'h': 480}}}}; const serverResponseVid = {'cur': 'USD', 'id': '25c6ab92aa0e81', 'seatbid': [{'seat': '3', 'bid': [{'crid': '1855', 'h': 480, 'protocol': 2, 'nurl': 'http://nep.advangelists.com/xp/evt?pp=1MO1wiaMhhq7wLRzZZwwwPkJxxKpYEnM5k5MH4qSGm1HR8rp3Nl7vDocvzZzSAvE4pnREL9mQ1kf5PDjk6E8em6DOk7vVrYUH1TYQyqCucd58PFpJNN7h30RXKHHFg3XaLuQ3PKfMuI1qZATBJ6WHcu875y0hqRdiewn0J4JsCYF53M27uwmcV0HnQxARQZZ72mPqrW95U6wgkZljziwKrICM3aBV07TU6YK5R5AyzJRuD6mtrQ2xtHlQ3jXVYKE5bvWFiUQd90t0jOGhPtYBNoOfP7uQ4ZZj4pyucxbr96orHe9PSOn9UpCSWArdx7s8lOfDpwOvbMuyGxynbStDWm38sDgd4bMHnIt762m5VMDNJfiUyX0vWzp05OsufJDVEaWhAM62i40lQZo7mWP4ipoOWLkmlaAzFIMsTcNaHAHiKKqGEOZLkCEhFNM0SLcvgN2HFRULOOIZvusq7TydOKxuXgCS91dLUDxDDDFUK83BFKlMkTxnCzkLbIR1bd9GKcr1TRryOrulyvRWAKAIhEsUzsc5QWFUhmI2dZ1eqnBQJ0c89TaPcnoaP2WipF68UgyiOstf2CBy0M34858tC5PmuQwQYwXscg6zyqDwR0i9MzGH4FkTyU5yeOlPcsA0ht6UcoCdFpHpumDrLUwAaxwGk1Nj8S6YlYYT5wNuTifDGbg22QKXzZBkUARiyVvgPn9nRtXnrd7WmiMYq596rya9RQj7LC0auQW8bHVQLEe49shsZDnAwZTWr4QuYKqgRGZcXteG7RVJe0ryBZezOq11ha9C0Lv0siNVBahOXE35Wzoq4c4BDaGpqvhaKN7pjeWLGlQR04ufWekwxiMWAvjmfgAfexBJ7HfbYNZpq__', 'adid': '61_1855', 'adomain': ['chevrolet.com.ar'], 'price': 2, 'w': 320, 'iurl': 'https://daf37cpxaja7f.cloudfront.net/c61/creative_url_14922301369663_1.png', 'cat': ['IAB2'], 'id': '7f570b40-aca1-4806-8ea8-818ea679c82b_0', 'attr': [], 'impid': '0', 'cid': '61'}]}], 'bidid': '7f570b40-aca1-4806-8ea8-818ea679c82b'} - const bidResponseVid = spec.interpretResponse({ body: serverResponseVid }, avngbidreqVid); + const bidResponseVid = spec.interpretResponse({ body: serverResponseVid }, advangelistsbidreqVid); delete bidResponseVid['vastUrl']; delete bidResponseVid['ad']; expect(bidResponseVid).to.deep.equal({ requestId: bidRequestsVid[0].bidId, - bidderCode: 'avng', + bidderCode: 'advangelists', creativeId: serverResponseVid.seatbid[0].bid[0].crid, cpm: serverResponseVid.seatbid[0].bid[0].price, width: serverResponseVid.seatbid[0].bid[0].w, @@ -106,10 +106,10 @@ describe('advangelistsBidAdapter', function () { }); it('should return valid banner bid responses', function () { - const avngbidreq = {bids: {}}; + const advangelistsbidreq = {bids: {}}; bidRequests.forEach(bid => { let _mediaTypes = (bid.mediaTypes && bid.mediaTypes.video ? VIDEO : BANNER); - avngbidreq.bids[bid.bidId] = {mediaTypes: _mediaTypes, + advangelistsbidreq.bids[bid.bidId] = {mediaTypes: _mediaTypes, w: _mediaTypes == BANNER ? bid.mediaTypes[_mediaTypes].sizes[0][0] : bid.mediaTypes[_mediaTypes].playerSize[0], h: _mediaTypes == BANNER ? bid.mediaTypes[_mediaTypes].sizes[0][1] : bid.mediaTypes[_mediaTypes].playerSize[1] @@ -117,11 +117,11 @@ describe('advangelistsBidAdapter', function () { }); const serverResponse = {'id': '2aa73f571eaf29', 'seatbid': [{'bid': [{'id': '2c5e8a1a84522d', 'impid': '2c5e8a1a84522d', 'price': 0.81, 'adid': 'abcde-12345', 'nurl': '', 'adm': '