diff --git a/README.md b/README.md index a89f68b8abc..9916768e1f7 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,15 @@ Working examples can be found in [the developer docs](http://prebid.org/dev-docs *Note:* Because we have transitioned to using gulp 4.0 - you need to have `gulp-cli` installed globally prior to running the general `npm install`. Run the following command to perform the install: `npm install gulp-cli -g` If you have a previous version of `gulp` installed globally, you'll need to remove it before installing `gulp-cli`. This removal can be done with the command: `npm rm gulp -g` +*Note:* In the 1.24.0 release of Prebid.js we have transitioned to using gulp 4.0 from using gulp 3.9.1. To compily with gulp's recommended setup for 4.0, you'll need to have `gulp-cli` installed globally prior to running the general `npm install`. This shouldn't impact any other projects you may work on that use an earlier version of gulp in it's setup. + +If you have a previous version of `gulp` installed globally, you'll need to remove it before installing `gulp-cli`. You can check if this is installed by running `gulp -v` and seeing the version that's listed in the `CLI` field of the output. If you have the `gulp` package installd globally, it's likely the same version that you'll see in the `Local` field. If you already have `gulp-cli` installed, it should be a lower major version (it's at version `2.0.1` at the time of the transition). + +To remove the old package, you can use the command: `npm rm gulp -g` + +Once setup, run the following command to globally install the `gulp-cli` package: `npm install gulp-cli -g` + + ## Build for Development diff --git a/integrationExamples/gpt/pbjs_example_gpt.html b/integrationExamples/gpt/pbjs_example_gpt.html index 536bc5a655d..88d4839d984 100644 --- a/integrationExamples/gpt/pbjs_example_gpt.html +++ b/integrationExamples/gpt/pbjs_example_gpt.html @@ -302,6 +302,16 @@ channelCode: 2264002816, //REQUIRED dimId: 9 //REQUIRED } + }, + { + bidder: 'openxoutstream', + params: { + unit: '53943996499', + delDomain: 'se-demo-d.openx.net', + publisher_page_url: 'yieldmo.com', + width: '300', + height: '250', + } } ] diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index 0595fc890f0..284c3f57406 100755 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -3,8 +3,10 @@ import { registerBidder } from 'src/adapters/bidderFactory'; import { parse } from 'src/url'; import * as utils from 'src/utils'; import find from 'core-js/library/fn/array/find'; +import JSEncrypt from 'jsencrypt/bin/jsencrypt'; +import sha256 from 'crypto-js/sha256'; -const ADAPTER_VERSION = 11; +const ADAPTER_VERSION = 14; const BIDDER_CODE = 'criteo'; const CDB_ENDPOINT = '//bidder.criteo.com/cdb'; const CRITEO_VENDOR_ID = 91; @@ -17,6 +19,13 @@ const PROFILE_ID_PUBLISHERTAG = 185; // Unminified source code can be found in: https://github.com/Prebid-org/prebid-js-external-js-criteo/blob/master/dist/prod.js const PUBLISHER_TAG_URL = '//static.criteo.net/js/ld/publishertag.prebid.js'; +export const FAST_BID_PUBKEY = `-----BEGIN PUBLIC KEY----- +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDO1BjAITkFTtP0IMzmF7qsqhpu +y1dGaTPHnjMU9mRZsrnfR3C0sEN5pYEzEcFRPnkJjJuhH8Rnh5+CE+LcKg0Z8ZZ7 +OmOSj0/qnYTAYCu0cR5LiyWG79KlIgUyMbp92ulGg24gAyGrVn4+v/4c53WlOEUp +4YWvb82G0CD5NcDNpQIDAQAB +-----END PUBLIC KEY-----`; + /** @type {BidderSpec} */ export const spec = { code: BIDDER_CODE, @@ -251,6 +260,34 @@ function createNativeAd(id, payload, callback) { `; } +export function cryptoVerify(key, hash, code) { + var jse = new JSEncrypt(); + jse.setPublicKey(key); + return jse.verify(code, hash, sha256); +} + +function validateFastBid(fastBid) { + // The value stored must contain the file's encrypted hash as first line + const firstLineEnd = fastBid.indexOf('\n'); + const firstLine = fastBid.substr(0, firstLineEnd).trim(); + if (firstLine.substr(0, 9) !== '// Hash: ') { + utils.logWarn('No hash found in FastBid'); + return false; + } + + // Remove the hash part from the locally stored value + const fileEncryptedHash = firstLine.substr(9); + const publisherTag = fastBid.substr(firstLineEnd + 1); + + // Verify the hash using cryptography + try { + return cryptoVerify(FAST_BID_PUBKEY, fileEncryptedHash, publisherTag); + } catch (e) { + utils.logWarn('Failed to verify Criteo FastBid'); + return undefined; + } +} + /** * @return {boolean} */ @@ -258,13 +295,17 @@ function tryGetCriteoFastBid() { try { const fastBid = localStorage.getItem('criteo_fast_bid'); if (fastBid !== null) { - eval(fastBid); // eslint-disable-line no-eval - return true; + if (validateFastBid(fastBid) === false) { + utils.logWarn('Invalid Criteo FastBid found'); + localStorage.removeItem('criteo_fast_bid'); + } else { + utils.logInfo('Using Criteo FastBid'); + eval(fastBid); // eslint-disable-line no-eval + } } } catch (e) { // Unable to get fast bid } - return false; } registerBidder(spec); diff --git a/modules/districtmDmxAdapter.js b/modules/districtmDmxAdapter.js new file mode 100644 index 00000000000..3ec0b72649e --- /dev/null +++ b/modules/districtmDmxAdapter.js @@ -0,0 +1,155 @@ +import * as utils from 'src/utils'; +import { registerBidder } from 'src/adapters/bidderFactory'; +import {config} from 'src/config'; + +const BIDDER_CODE = 'districtmDMX'; + +const DMXURI = 'https://dmx.districtm.io/b/v1'; + +export const spec = { + code: BIDDER_CODE, + supportedFormat: ['banner'], + isBidRequestValid(bid) { + return !!(bid.params.dmxid && bid.params.memberid); + }, + interpretResponse(response, bidRequest) { + response = response.body || {}; + if (response.seatbid) { + if (utils.isArray(response.seatbid)) { + const {seatbid} = response; + let winners = seatbid.reduce((bid, ads) => { + let ad = ads.bid.reduce(function(oBid, nBid) { + if (oBid.price < nBid.price) { + const bid = matchRequest(nBid.impid, bidRequest); + const {width, height} = defaultSize(bid); + nBid.cpm = nBid.price; + nBid.bidId = nBid.impid; + nBid.requestId = nBid.impid; + nBid.width = nBid.w || width; + nBid.height = nBid.h || height; + nBid.ad = nBid.adm; + nBid.netRevenue = true; + nBid.creativeId = nBid.crid; + nBid.currency = 'USD'; + nBid.ttl = 60; + + return nBid; + } else { + oBid.cpm = oBid.price; + return oBid; + } + }, {price: 0}); + if (ad.adm) { + bid.push(ad) + } + return bid; + }, []) + let winnersClean = winners.filter(w => { + if (w.bidId) { + return true; + } + return false; + }); + return winnersClean; + } else { + return []; + } + } else { + return []; + } + }, + buildRequests(bidRequest, bidderRequest) { + let timeout = config.getConfig('bidderTimeout'); + let dmxRequest = { + id: utils.generateUUID(), + cur: ['USD'], + tmax: (timeout - 300), + test: this.test() || 0, + site: { + publisher: { id: String(bidRequest[0].params.memberid) || null } + } + } + if (!dmxRequest.test) { + delete dmxRequest.test; + } + if (bidderRequest.gdprConsent) { + dmxRequest.regs = {}; + dmxRequest.regs.ext = {}; + dmxRequest.regs.ext.gdpr = bidderRequest.gdprConsent.gdprApplies === true ? 1 : 0; + if (dmxRequest.regs.ext.gdpr) { + dmxRequest.regs.ext.consent = bidderRequest.gdprConsent.consentString; + } + } + let tosendtags = bidRequest.map(dmx => { + var obj = {}; + obj.id = dmx.bidId; + obj.tagid = String(dmx.params.dmxid); + obj.secure = window.location.protocol === 'https:' ? 1 : 0; + obj.banner = { + topframe: 1, + w: dmx.sizes[0][0] || 0, + h: dmx.sizes[0][1] || 0, + format: dmx.sizes.map(s => { + return {w: s[0], h: s[1]}; + }) + }; + return obj; + }); + dmxRequest.imp = tosendtags; + return { + method: 'POST', + url: DMXURI, + data: JSON.stringify(dmxRequest), + options: { + contentType: 'application/json', + withCredentials: true + }, + bidderRequest + } + }, + test() { + return window.location.href.indexOf('dmTest=true') !== -1 ? 1 : 0; + }, + getUserSyncs(optionsType) { + if (optionsType.iframeEnabled) { + return [{ + type: 'iframe', + url: 'https://cdn.districtm.io/ids/index.html' + }]; + } + } +} + +/** + * Function matchRequest(id: string, BidRequest: object) + * @param id + * @type string + * @param bidRequest + * @type Object + * @returns Object + * + */ +export function matchRequest(id, bidRequest) { + const {bids} = bidRequest.bidderRequest; + const [returnValue] = bids.filter(bid => bid.bidId === id); + return returnValue; +} +export function checkDeepArray(Arr) { + if (Array.isArray(Arr)) { + if (Array.isArray(Arr[0])) { + return Arr[0]; + } else { + return Arr; + } + } else { + return Arr; + } +} +export function defaultSize(thebidObj) { + const {sizes} = thebidObj; + const returnObject = {}; + returnObject.width = checkDeepArray(sizes)[0]; + returnObject.height = checkDeepArray(sizes)[1]; + return returnObject; +} +registerBidder(spec); diff --git a/modules/districtmDmxAdapter.md b/modules/districtmDmxAdapter.md new file mode 100644 index 00000000000..a096029963c --- /dev/null +++ b/modules/districtmDmxAdapter.md @@ -0,0 +1,31 @@ +# Overview + +``` +Module Name: DistrictM Bid Adapter +Module Type: Bidder Adapter +Maintainer: steve@districtm.net +``` + +# Description + +Adapter that connects to DistrictM's demand sources. +This version only support banner + +# Test Parameters +``` + var adUnits = [{ + code: 'div-gpt-ad-1460505748561-0', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]], + } + }, + bids: [{ + bidder: 'districtmDMX', + params: { + dmxid: 100001, + memberid: 100003 + } + }] + }]; +``` diff --git a/modules/improvedigitalBidAdapter.js b/modules/improvedigitalBidAdapter.js index 2cc1d8b39b0..54eb3bb6574 100644 --- a/modules/improvedigitalBidAdapter.js +++ b/modules/improvedigitalBidAdapter.js @@ -5,7 +5,7 @@ import { config } from 'src/config'; const BIDDER_CODE = 'improvedigital'; export const spec = { - version: '4.3.0', + version: '4.4.0', code: BIDDER_CODE, aliases: ['id'], @@ -78,11 +78,24 @@ export const spec = { bid.cpm = parseFloat(bidObject.price); bid.creativeId = bidObject.crid; bid.currency = bidObject.currency ? bidObject.currency.toUpperCase() : 'USD'; - if (utils.isNumber(bidObject.lid)) { + + // Deal ID. Composite ads can have multiple line items and the ID of the first + // dealID line item will be used. + if (utils.isNumber(bidObject.lid) && bidObject.buying_type === 'deal_id') { bid.dealId = bidObject.lid; - } else if (typeof bidObject.lid === 'object' && bidObject.lid['1']) { - bid.dealId = bidObject.lid['1']; + } else if (Array.isArray(bidObject.lid) && + Array.isArray(bidObject.buying_type) && + bidObject.lid.length === bidObject.buying_type.length) { + let isDeal = false; + bidObject.buying_type.forEach((bt, i) => { + if (isDeal) return; + if (bt === 'deal_id') { + isDeal = true; + bid.dealId = bidObject.lid[i]; + } + }); } + bid.height = bidObject.h; bid.netRevenue = bidObject.isNet ? bidObject.isNet : false; bid.requestId = bidObject.id; diff --git a/modules/invibesBidAdapter.js b/modules/invibesBidAdapter.js index eefe63034bd..d6edff75ec9 100644 --- a/modules/invibesBidAdapter.js +++ b/modules/invibesBidAdapter.js @@ -8,7 +8,8 @@ const CONSTANTS = { TIME_TO_LIVE: 300, DEFAULT_CURRENCY: 'EUR', PREBID_VERSION: 1, - METHOD: 'GET' + METHOD: 'GET', + INVIBES_VENDOR_ID: 436 }; export const spec = { @@ -23,9 +24,7 @@ export const spec = { * @param bidderRequest * @return ServerRequest[] */ - buildRequests: function (bidRequests, bidderRequest) { - return buildRequest(bidRequests, bidderRequest != null ? bidderRequest.auctionStart : null); - }, + buildRequests: buildRequest, /** * @param {*} responseObj * @param {requestParams} bidRequests @@ -55,6 +54,11 @@ let invibes = topWin.invibes = topWin.invibes || {}; let _customUserSync; function isBidRequestValid(bid) { + if (invibes && typeof invibes.bidResponse === 'object') { + utils.logInfo('Invibes Adapter - Bid response already received. Invibes only responds to one bid request per user visit'); + return false; + } + if (typeof bid.params !== 'object') { return false; } @@ -67,11 +71,11 @@ function isBidRequestValid(bid) { return true; } -function buildRequest(bidRequests, auctionStart) { - // invibes only responds to 1 bid request for each user visit +function buildRequest(bidRequests, bidderRequest) { + bidderRequest = bidderRequest || {}; const _placementIds = []; let _loginId, _customEndpoint; - let _ivAuctionStart = auctionStart || Date.now(); + let _ivAuctionStart = bidderRequest.auctionStart || Date.now(); bidRequests.forEach(function (bidRequest) { bidRequest.startTime = new Date().getTime(); @@ -85,9 +89,9 @@ function buildRequest(bidRequests, auctionStart) { cookieDomain = detectTopmostCookieDomain(); invibes.noCookies = invibes.noCookies || invibes.getCookie('ivNoCookie'); - invibes.optIn = invibes.optIn || invibes.getCookie('ivOptIn'); + invibes.optIn = invibes.optIn || invibes.getCookie('ivOptIn') || readGdprConsent(bidderRequest.gdprConsent); - initDomainId(); + initDomainId(invibes.domainOptions); const currentQueryStringParams = parseQueryStringParams(); @@ -96,7 +100,6 @@ function buildRequest(bidRequests, auctionStart) { videoAdHtmlId: generateRandomId(), showFallback: currentQueryStringParams['advs'] === '0', ivbsCampIdsLocal: invibes.getCookie('IvbsCampIdsLocal'), - lId: invibes.dom.id, bidParamsJson: JSON.stringify({ placementIds: _placementIds, @@ -110,11 +113,12 @@ function buildRequest(bidRequests, auctionStart) { width: topWin.innerWidth, height: topWin.innerHeight, - noc: !cookieDomain + noc: !cookieDomain, + oi: invibes.optIn }; - if (invibes.optIn) { - data.oi = 1; + if (invibes.dom.id) { + data.lId = invibes.dom.id; } const parametersToPassForward = 'videoaddebug,advs,bvci,bvid,istop,trybvid,trybvci'.split(','); @@ -323,10 +327,7 @@ function initLogger() { function buildSyncUrl() { let syncUrl = _customUserSync || CONSTANTS.SYNC_ENDPOINT; syncUrl += '?visitId=' + invibes.visitId; - - if (invibes.optIn) { - syncUrl += '&optIn=1'; - } + syncUrl += '&optIn=' + invibes.optIn; const did = invibes.getCookie('ivbsdid'); if (did) { @@ -358,6 +359,14 @@ function acceptPostMessage(e) { } } +function readGdprConsent(gdprConsent) { + if (gdprConsent && gdprConsent.vendorData && gdprConsent.vendorData.vendorConsents) { + return !!gdprConsent.vendorData.vendorConsents[CONSTANTS.INVIBES_VENDOR_ID.toString(10)] === true ? 2 : -2; + } + + return 0; +} + const ivLogger = initLogger(); /// Local domain cookie management ===================== @@ -400,9 +409,9 @@ invibes.setCookie = function (name, value, exdays, domain) { let detectTopmostCookieDomain = function () { let testCookie = invibes.Uid.generate(); - let hostParts = location.host.split('.'); + let hostParts = location.hostname.split('.'); if (hostParts.length === 1) { - return location.host; + return location.hostname; } for (let i = hostParts.length - 1; i >= 0; i--) { let domain = '.' + hostParts.slice(i).join('.'); @@ -463,8 +472,8 @@ let initDomainId = function (options) { let setId = function () { invibes.dom = { - id: !state.cr && invibes.optIn ? state.id : undefined, - tempId: invibes.optIn ? state.id : undefined, + id: (!state.cr && invibes.optIn > 0) ? state.id : undefined, + tempId: (invibes.optIn > 0) ? state.id : undefined, graduate: graduate }; }; @@ -481,7 +490,7 @@ let initDomainId = function (options) { if (state.hc < minHC) { state.hc++; } - if (state.hc >= minHC && validGradTime(state)) { + if ((state.hc >= minHC && validGradTime(state)) || options.skipGraduation) { graduate(); } } @@ -496,4 +505,11 @@ export function resetInvibes() { invibes.noCookies = undefined; invibes.dom = undefined; invibes.bidResponse = undefined; + invibes.domainOptions = undefined; +} + +export function stubDomainOptions(persistence) { + invibes.domainOptions = { + persistence: persistence + }; } diff --git a/modules/livewrappedAnalyticsAdapter.js b/modules/livewrappedAnalyticsAdapter.js new file mode 100644 index 00000000000..21c0bc73ec4 --- /dev/null +++ b/modules/livewrappedAnalyticsAdapter.js @@ -0,0 +1,222 @@ +import * as utils from 'src/utils'; +import {ajax} from 'src/ajax'; +import adapter from 'src/AnalyticsAdapter'; +import CONSTANTS from 'src/constants.json'; +import adaptermanager from 'src/adaptermanager'; + +const ANALYTICSTYPE = 'endpoint'; +const URL = '//lwadm.com/analytics/10'; +const EMPTYURL = ''; +const REQUESTSENT = 1; +const RESPONSESENT = 2; +const WINSENT = 4; +const TIMEOUTSENT = 8; + +let initOptions; +export const BID_WON_TIMEOUT = 500; + +const cache = { + auctions: {}, +}; + +let livewrappedAnalyticsAdapter = Object.assign(adapter({EMPTYURL, ANALYTICSTYPE}), { + track({eventType, args}) { + utils.logInfo('LIVEWRAPPED_EVENT:', [eventType, args]); + + switch (eventType) { + case CONSTANTS.EVENTS.AUCTION_INIT: + utils.logInfo('LIVEWRAPPED_AUCTION_INIT:', args); + cache.auctions[args.auctionId] = {bids: {}}; + break; + case CONSTANTS.EVENTS.BID_REQUESTED: + utils.logInfo('LIVEWRAPPED_BID_REQUESTED:', args); + + args.bids.forEach(function(bidRequest) { + cache.auctions[args.auctionId].timeStamp = args.start; + cache.auctions[args.auctionId].bids[bidRequest.bidId] = { + bidder: bidRequest.bidder, + adUnit: bidRequest.adUnitCode, + isBid: false, + won: false, + timeout: false, + sendStatus: 0 + } + + utils.logInfo(bidRequest); + }) + utils.logInfo(livewrappedAnalyticsAdapter.requestEvents); + break; + case CONSTANTS.EVENTS.BID_RESPONSE: + utils.logInfo('LIVEWRAPPED_BID_RESPONSE:', args); + + let bidResponse = cache.auctions[args.auctionId].bids[args.adId]; + bidResponse.isBid = args.getStatusCode() === CONSTANTS.STATUS.GOOD; + bidResponse.width = args.width; + bidResponse.height = args.height; + bidResponse.cpm = args.cpm; + bidResponse.ttr = args.timeToRespond; + break; + case CONSTANTS.EVENTS.BIDDER_DONE: + utils.logInfo('LIVEWRAPPED_BIDDER_DONE:', args); + args.bids.forEach(doneBid => { + let bid = cache.auctions[doneBid.auctionId].bids[doneBid.bidId]; + if (!bid.ttr) { + bid.ttr = Date.now() - args.auctionStart; + } + }); + break; + case CONSTANTS.EVENTS.BID_WON: + utils.logInfo('LIVEWRAPPED_BID_WON:', args); + let wonBid = cache.auctions[args.auctionId].bids[args.adId]; + wonBid.won = true; + if (wonBid.sendStatus != 0) { + livewrappedAnalyticsAdapter.sendEvents(); + } + break; + case CONSTANTS.EVENTS.BID_TIMEOUT: + utils.logInfo('LIVEWRAPPED_BID_TIMEOUT:', args); + args.forEach(timeout => { + cache.auctions[timeout.auctionId].bids[timeout.bidId].timeout = true; + }); + break; + case CONSTANTS.EVENTS.AUCTION_END: + utils.logInfo('LIVEWRAPPED_AUCTION_END:', args); + setTimeout(() => { + livewrappedAnalyticsAdapter.sendEvents(); + }, BID_WON_TIMEOUT); + break; + } + } +}); + +// save the base class function +livewrappedAnalyticsAdapter.originEnableAnalytics = livewrappedAnalyticsAdapter.enableAnalytics; +livewrappedAnalyticsAdapter.allRequestEvents = []; + +// override enableAnalytics so we can get access to the config passed in from the page +livewrappedAnalyticsAdapter.enableAnalytics = function (config) { + initOptions = config.options; + livewrappedAnalyticsAdapter.originEnableAnalytics(config); +}; + +livewrappedAnalyticsAdapter.sendEvents = function() { + var events = { + publisherId: initOptions.publisherId, + requests: getSentRequests(), + responses: getResponses(), + wins: getWins(), + timeouts: getTimeouts() + }; + + if (events.requests.length == 0 && + events.responses.length == 0 && + events.wins.length == 0 && + events.timeouts.length == 0) { + return; + } + + ajax(URL, undefined, JSON.stringify(events), {method: 'POST'}); +} + +function getSentRequests() { + var sentRequests = []; + + Object.keys(cache.auctions).forEach(auctionId => { + Object.keys(cache.auctions[auctionId].bids).forEach(bidId => { + let auction = cache.auctions[auctionId]; + let bid = auction.bids[bidId]; + if (!(bid.sendStatus & REQUESTSENT)) { + bid.sendStatus |= REQUESTSENT; + + sentRequests.push({ + timeStamp: auction.timeStamp, + adUnit: bid.adUnit, + bidder: bid.bidder + }); + } + }); + }); + + return sentRequests; +} + +function getResponses() { + var responses = []; + + Object.keys(cache.auctions).forEach(auctionId => { + Object.keys(cache.auctions[auctionId].bids).forEach(bidId => { + let auction = cache.auctions[auctionId]; + let bid = auction.bids[bidId]; + if (!(bid.sendStatus & RESPONSESENT) && !bid.timeout) { + bid.sendStatus |= RESPONSESENT; + + responses.push({ + timeStamp: auction.timeStamp, + adUnit: bid.adUnit, + bidder: bid.bidder, + width: bid.width, + height: bid.height, + cpm: bid.cpm, + ttr: bid.ttr, + IsBid: bid.isBid + }); + } + }); + }); + + return responses; +} + +function getWins() { + var wins = []; + + Object.keys(cache.auctions).forEach(auctionId => { + Object.keys(cache.auctions[auctionId].bids).forEach(bidId => { + let auction = cache.auctions[auctionId]; + let bid = auction.bids[bidId]; + if (!(bid.sendStatus & WINSENT) && bid.won) { + bid.sendStatus |= WINSENT; + + wins.push({ + timeStamp: auction.timeStamp, + adUnit: bid.adUnit, + bidder: bid.bidder, + width: bid.width, + height: bid.height, + cpm: bid.cpm, + }); + } + }); + }); + + return wins; +} + +function getTimeouts() { + var timeouts = []; + + Object.keys(cache.auctions).forEach(auctionId => { + Object.keys(cache.auctions[auctionId].bids).forEach(bidId => { + let auction = cache.auctions[auctionId]; + let bid = auction.bids[bidId]; + if (!(bid.sendStatus & TIMEOUTSENT) && bid.timeout) { + bid.sendStatus |= TIMEOUTSENT; + + timeouts.push({ + bidder: bid.bidder, + adUnit: bid.adUnit, + timeStamp: auction.timeStamp + }); + } + }); + }); + + return timeouts; +} + +adaptermanager.registerAnalyticsAdapter({ + adapter: livewrappedAnalyticsAdapter, + code: 'livewrapped' +}); + +export default livewrappedAnalyticsAdapter; diff --git a/modules/livewrappedBidAdapter.js b/modules/livewrappedBidAdapter.js new file mode 100644 index 00000000000..ebfe4aad2d4 --- /dev/null +++ b/modules/livewrappedBidAdapter.js @@ -0,0 +1,187 @@ +import * as utils from 'src/utils'; +import { registerBidder } from 'src/adapters/bidderFactory'; +import { config } from 'src/config'; +import find from 'core-js/library/fn/array/find'; + +const BIDDER_CODE = 'livewrapped'; +export const URL = 'https://lwadm.com/ad'; +const VERSION = '1.1'; + +export const spec = { + code: BIDDER_CODE, + + /** + * Determines whether or not the given bid request is valid. + * + * Parameters should be + * + * adUnitId: LiveWrapped's id of the ad unit. Optional. A guid identifying the ad unit. + * adUnitName: LiveWrapped's name of the ad unit Optional. (Prebid's ad unit code will be used otherwise.) + * publisherId: Publisher id. Required if adUnitName is used or both adUnitName and adUnitId is omitted, otherwise optional. + * userId: A persistent user id if available. Optional. + * url: Page url Optional. Use if page url cannot be determined due to use of iframes. + * bidUrl: Bidding endpoint Optional. + * seats: List of bidders and seats Optional. {"bidder name": ["seat 1", "seat 2"], ...} + * deviceId: Device id if available Optional. + * ifa: Advertising ID Optional. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function(bid) { + return (bid.params.adUnitId || ((bid.params.adUnitName || bid.adUnitCode || bid.placementCode) && bid.params.publisherId)) !== undefined; + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} bidRequests A non-empty list of bid requests which should be sent to the Server. + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function(bidRequests, bidderRequest) { + const userId = find(bidRequests, hasUserId); + const publisherId = find(bidRequests, hasPublisherId); + const auctionId = find(bidRequests, hasAuctionId); + let bidUrl = find(bidRequests, hasBidUrl); + let url = find(bidRequests, hasUrl); + let test = find(bidRequests, hasTestParam); + let seats = find(bidRequests, hasSeatsParam); + let deviceId = find(bidRequests, hasDeviceIdParam); + let ifa = find(bidRequests, hasIfaParam); + let tid = find(bidRequests, hasTidParam); + bidUrl = bidUrl ? bidUrl.params.bidUrl : URL; + url = url ? url.params.url : (config.getConfig('pageUrl') || utils.getTopWindowUrl()); + test = test ? test.params.test : undefined; + var adRequests = bidRequests.map(bidToAdRequest); + + const payload = { + auctionId: auctionId ? auctionId.auctionId : undefined, + publisherId: publisherId ? publisherId.params.publisherId : undefined, + userId: userId ? userId.params.userId : undefined, + url: url, + test: test, + seats: seats ? seats.params.seats : undefined, + deviceId: deviceId ? deviceId.params.deviceId : undefined, + ifa: ifa ? ifa.params.ifa : undefined, + tid: tid ? tid.params.tid : undefined, + version: VERSION, + gdprApplies: bidderRequest.gdprConsent ? bidderRequest.gdprConsent.gdprApplies : false, + gdprConsent: bidderRequest.gdprConsent ? bidderRequest.gdprConsent.consentString : undefined, + cookieSupport: !utils.isSafariBrowser() && utils.cookiesAreEnabled(), + adRequests: [...adRequests] + }; + const payloadString = JSON.stringify(payload); + return { + method: 'POST', + url: bidUrl, + data: payloadString, + }; + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {*} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function(serverResponse) { + const bidResponses = []; + + serverResponse.body.ads.forEach(function(ad) { + let bidResponse = { + requestId: ad.bidId, + bidderCode: BIDDER_CODE, + cpm: ad.cpmBid, + width: ad.width, + height: ad.height, + ad: ad.tag, + ttl: ad.ttl, + creativeId: ad.creativeId, + netRevenue: true, + currency: serverResponse.body.currency + }; + + bidResponses.push(bidResponse); + }); + + return bidResponses; + }, + + getUserSyncs: function(syncOptions, serverResponses) { + if (serverResponses.length == 0) return []; + + let syncList = []; + let userSync = serverResponses[0].body.pixels || []; + + userSync.forEach(function(sync) { + if (syncOptions.pixelEnabled && sync.type == 'Redirect') { + syncList.push({type: 'image', url: sync.url}); + } + + if (syncOptions.iframeEnabled && sync.type == 'Iframe') { + syncList.push({type: 'iframe', url: sync.url}); + } + }); + + return syncList; + } +} + +function hasUserId(bid) { + return !!bid.params.userId; +} + +function hasPublisherId(bid) { + return !!bid.params.publisherId; +} + +function hasUrl(bid) { + return !!bid.params.url; +} + +function hasBidUrl(bid) { + return !!bid.params.bidUrl; +} + +function hasAuctionId(bid) { + return !!bid.auctionId; +} + +function hasTestParam(bid) { + return !!bid.params.test; +} + +function hasSeatsParam(bid) { + return !!bid.params.seats; +} + +function hasDeviceIdParam(bid) { + return !!bid.params.deviceId; +} + +function hasIfaParam(bid) { + return !!bid.params.ifa; +} + +function hasTidParam(bid) { + return !!bid.params.tid; +} + +function bidToAdRequest(bid) { + return { + adUnitId: bid.params.adUnitId, + callerAdUnitId: bid.params.adUnitName || bid.adUnitCode || bid.placementCode, + bidId: bid.bidId, + transactionId: bid.transactionId, + formats: bid.sizes.map(sizeToFormat) + }; +} + +function sizeToFormat(size) { + return { + width: size[0], + height: size[1] + } +} + +registerBidder(spec); diff --git a/modules/livewrappedBidAdapter.md b/modules/livewrappedBidAdapter.md new file mode 100644 index 00000000000..15e3e8d533a --- /dev/null +++ b/modules/livewrappedBidAdapter.md @@ -0,0 +1,28 @@ +# Overview + +**Module Name**: Livewrapped Bid Adapter +**Module Type**: Bidder Adapter +**Maintainer**: info@livewrapped.com + +# Description + +Connects to Livewrapped Header Bidding wrapper for bids. + +Livewrapped supports banner. + +# Test Parameters + +``` +var adUnits = [ + { + code: 'banner-div', + sizes: [[300, 250], [300,600]], + bids: [{ + bidder: 'livewrapped', + params: { + adUnitId: '6A32352E-BC17-4B94-B2A7-5BF1724417D7' + } + }] + } +]; +``` diff --git a/modules/openxBidAdapter.js b/modules/openxBidAdapter.js index 952e706ef7d..ee1fa58a4e9 100644 --- a/modules/openxBidAdapter.js +++ b/modules/openxBidAdapter.js @@ -8,7 +8,7 @@ import {parse} from 'src/url'; const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; const BIDDER_CODE = 'openx'; const BIDDER_CONFIG = 'hb_pb'; -const BIDDER_VERSION = '2.1.4'; +const BIDDER_VERSION = '2.1.5'; let shouldSendBoPixel = true; @@ -222,6 +222,10 @@ function buildCommonQueryParamsFromBids(bids, bidderRequest) { } } + if (bids[0].crumbs && bids[0].crumbs.pubcid) { + defaultParams.pubcid = bids[0].crumbs.pubcid; + } + return defaultParams; } diff --git a/modules/openxoutstreamBidAdapter.js b/modules/openxoutstreamBidAdapter.js new file mode 100644 index 00000000000..6da234284c6 --- /dev/null +++ b/modules/openxoutstreamBidAdapter.js @@ -0,0 +1,214 @@ +import { config } from 'src/config'; +import { registerBidder } from 'src/adapters/bidderFactory'; +import * as utils from 'src/utils'; +import { BANNER } from 'src/mediaTypes'; + +const SUPPORTED_AD_TYPES = [BANNER]; +const BIDDER_CODE = 'openxoutstream'; +const BIDDER_CONFIG = 'hb_pb_ym'; +const BIDDER_VERSION = '1.0.0'; +const CURRENCY = 'USD'; +const NET_REVENUE = true; +const TIME_TO_LIVE = 300; +const YM_SCRIPT = `!function(e,t){if(void 0===t._ym){var a=Math.round(5*Math.random()/3)+'';t._ym='';var m=e.createElement('script');m.type='text/javascript',m.async=!0,m.src='//static.yieldmo.com/ym.'+a+'.js',(e.getElementsByTagName('head')[0]||e.getElementsByTagName('body')[0]).appendChild(m)}else t._ym instanceof String||void 0===t._ym.chkPls||t._ym.chkPls()}(document,window);`; +const PLACEMENT_ID = '1986307928000988495'; +const PUBLISHER_ID = '1986307525700126029'; +const CR_ID = '2052941939925262540'; +const AD_ID = '1991358644725162800'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: SUPPORTED_AD_TYPES, + isBidRequestValid: function(bidRequest) { + if (bidRequest.params.delDomain) { + return !!bidRequest.params.unit || utils.deepAccess(bidRequest, 'mediaTypes.banner.sizes.length') > 0; + } + return false; + }, + buildRequests: function(bidRequests, bidderRequest) { + if (bidRequests.length === 0) { + return []; + } + let requests = []; + requests.push(buildOXBannerRequest(bidRequests, bidderRequest)); + return requests; + }, + interpretResponse: function(serverResponse, serverRequest) { + return handleVastResponse(serverResponse, serverRequest.payload) + }, + + transformBidParams: function(params, isOpenRtb) { + return utils.convertTypes({ + 'unit': 'string', + }, params); + } +}; + +function getViewportDimensions(isIfr) { + let width; + let height; + let tWin = window; + let body; + + if (isIfr) { + let tDoc; + try { + tWin = window.top; + tDoc = window.top.document; + } catch (e) { + return; + } + body = tDoc.body; + + width = tWin.innerWidth || docEl.clientWidth || body.clientWidth; + height = tWin.innerHeight || docEl.clientHeight || body.clientHeight; + } else { + width = tWin.innerWidth || docEl.clientWidth; + height = tWin.innerHeight || docEl.clientHeight; + } + + return `${width}x${height}`; +} + +function buildCommonQueryParamsFromBids(bids, bidderRequest) { + const isInIframe = utils.inIframe(); + let defaultParams; + defaultParams = { + ju: config.getConfig('pageUrl') || utils.getTopWindowUrl(), + jr: utils.getTopWindowReferrer(), + ch: document.charSet || document.characterSet, + res: `${screen.width}x${screen.height}x${screen.colorDepth}`, + ifr: isInIframe, + tz: new Date().getTimezoneOffset(), + tws: getViewportDimensions(isInIframe), + be: 1, + bc: bids[0].params.bc || `${BIDDER_CONFIG}_${BIDDER_VERSION}`, + auid: '540141567', + dddid: utils._map(bids, bid => bid.transactionId).join(','), + openrtb: '%7B%22mimes%22%3A%5B%22video%2Fmp4%22%5D%7D', + nocache: new Date().getTime() + }; + + if (utils.deepAccess(bidderRequest, 'gdprConsent')) { + let gdprConsentConfig = bidderRequest.gdprConsent; + + if (gdprConsentConfig.consentString !== undefined) { + defaultParams.gdpr_consent = gdprConsentConfig.consentString; + } + + if (gdprConsentConfig.gdprApplies !== undefined) { + defaultParams.gdpr = gdprConsentConfig.gdprApplies ? 1 : 0; + } + + if (config.getConfig('consentManagement.cmpApi') === 'iab') { + defaultParams.x_gdpr_f = 1; + } + } + + return defaultParams; +} + +function buildOXBannerRequest(bids, bidderRequest) { + let queryParams = buildCommonQueryParamsFromBids(bids, bidderRequest); + queryParams.aus = utils._map(bids, bid => utils.parseSizesInput(bid.sizes).join(',')).join('|'); + + if (bids.some(bid => bid.params.doNotTrack)) { + queryParams.ns = 1; + } + + if (bids.some(bid => bid.params.coppa)) { + queryParams.tfcd = 1; + } + + let url = `https://${bids[0].params.delDomain}/v/1.0/avjp` + return { + method: 'GET', + url: url, + data: queryParams, + payload: {'bids': bids} + }; +} + +function handleVastResponse(response, serverResponse) { + const body = response.body + let bidResponses = []; + if (response !== undefined && body.vastUrl !== '' && body.pub_rev && body.pub_rev > 0) { + const openHtmlTag = '
'; + const closeHtmlTag = ''; + const sdkScript = createSdkScript().outerHTML; + const placementDiv = createPlacementDiv(); + placementDiv.dataset.pId = PUBLISHER_ID; + const placementDivString = placementDiv.outerHTML; + const adResponse = getTemplateAdResponse(body.vastUrl); + const adResponseString = JSON.stringify(adResponse); + const ymAdsScript = ''; + + let bidResponse = {}; + bidResponse.requestId = serverResponse.bids[0].bidId; + bidResponse.bidderCode = BIDDER_CODE; + bidResponse.netRevenue = NET_REVENUE; + bidResponse.currency = CURRENCY; + bidResponse.cpm = Number(body.pub_rev) / 1000; + bidResponse.creativeId = body.adid; + bidResponse.height = body.height; + bidResponse.width = body.width; + bidResponse.vastUrl = body.vastUrl; + bidResponse.ttl = TIME_TO_LIVE; + bidResponse.mediaType = BANNER; + bidResponse.ad = openHtmlTag + placementDivString + ymAdsScript + sdkScript + closeHtmlTag; + + bidResponses.push(bidResponse); + } + return bidResponses; +} +registerBidder(spec); + +// HELPER FUNCTIONS +function createSdkScript() { + const script = document.createElement('script'); + script.innerHTML = YM_SCRIPT; + return script; +} +function createPlacementDiv() { + const div = document.createElement('div'); + div.id = `ym_${PLACEMENT_ID}`; + div.classList.add('ym'); + div.dataset.lfId = CR_ID; + return div +} + +/** + * Create a nativeplay template with the placement id and vastURL. + * @param vastUrl + */ +const getTemplateAdResponse = (vastUrl) => { + return { + availability_zone: 'us-east-1a', + data: [ + { + ads: [ + { + actions: {}, + adv_id: AD_ID, + configurables: { + cta_button_copy: 'Learn More', + vast_click_tracking: 'true', + vast_url: vastUrl, + }, + cr_id: CR_ID, + } + ], + column_count: 1, + configs: { + allowable_height: '248', + header_copy: 'You May Like', + ping: 'true', + }, + creative_format_id: 40, + css: '', + placement_id: PLACEMENT_ID, + } + ], + nc: 0, + }; +}; diff --git a/modules/openxoutstreamBidAdapter.md b/modules/openxoutstreamBidAdapter.md new file mode 100644 index 00000000000..a77b4560f97 --- /dev/null +++ b/modules/openxoutstreamBidAdapter.md @@ -0,0 +1,41 @@ +# Overview + +``` +Module Name: OpenX Outstream Bidder Adapter +Module Type: Bidder Adapter +Maintainer: opensource@yieldmo.com, jimmy.tu@openx.com +Note: Ads will only render in mobile +``` + +# Description + +Module that connects to OpenX's demand sources for outstream to Yieldmo. + +This bid adapter supports Banner. + +# Example +```javascript +var adUnits = [ + { + code: 'test-div', + sizes: [[300, 250]], // a display size + mediaTypes: {'banner': {}}, + bids: [ + { + bidder: 'openxoutstream', + params: { + unit: '53943996499', + delDomain: 'se-demo-d.openx.net', + publisher_page_url: 'yieldmo.com', + width: '300', + height: '250', + } + } + ] + } +]; +``` + +# Additional Details +[Banner Ads](https://docs.openx.com/Content/developers/containers/prebid-adapter.html) + diff --git a/modules/playgroundxyzBidAdapter.js b/modules/playgroundxyzBidAdapter.js index e54f93ab8ca..f6a85b6522e 100644 --- a/modules/playgroundxyzBidAdapter.js +++ b/modules/playgroundxyzBidAdapter.js @@ -3,11 +3,11 @@ import { registerBidder } from 'src/adapters/bidderFactory'; import { BANNER } from 'src/mediaTypes'; const BIDDER_CODE = 'playgroundxyz'; -const URL = 'https://ads.playground.xyz/host-config/prebid'; +const URL = 'https://ads.playground.xyz/host-config/prebid?v=2'; export const spec = { code: BIDDER_CODE, - aliases: ['playgroundxyz'], + aliases: ['playgroundxyz', 'pxyz'], supportedMediaTypes: [BANNER], /** @@ -69,8 +69,10 @@ export const spec = { if (!serverResponse || serverResponse.error) { let errorMessage = `in response for ${bidderRequest.bidderCode} adapter`; - if (serverResponse && serverResponse.error) { errorMessage += `: ${serverResponse.error}`; } - utils.logError(errorMessage); + if (serverResponse && serverResponse.error) { + errorMessage += `: ${serverResponse.error}`; + utils.logError(errorMessage); + } return bids; } @@ -80,6 +82,10 @@ export const spec = { return bids; } + if (!serverResponse.seatbid) { + return bids; + } + serverResponse.seatbid.forEach(sBid => { if (sBid.hasOwnProperty('bid')) { sBid.bid.forEach(iBid => { @@ -131,6 +137,12 @@ function mapImpression(bid) { ext: { appnexus: { placement_id: parseInt(bid.params.placementId, 10) + }, + pxyz: { + adapter: { + vendor: 'prebid', + prebid: '$prebid.version$' + } } } }; diff --git a/modules/polymorphBidAdapter.js b/modules/polymorphBidAdapter.js index 8d07a66d0da..da5f7f711ec 100644 --- a/modules/polymorphBidAdapter.js +++ b/modules/polymorphBidAdapter.js @@ -2,8 +2,18 @@ import * as utils from 'src/utils'; import { registerBidder } from 'src/adapters/bidderFactory'; import { BANNER } from 'src/mediaTypes'; +const PROTOCOL = getProtocol(); const BIDDER_CODE = 'polymorph'; const URL = '//api.adsnative.com/v1/ad-template.json'; +const USER_SYNC_URL = PROTOCOL + '//rudy.adsnative.com/cm.gif'; + +function getProtocol() { + if (location.protocol && location.protocol.indexOf('https') === 0) { + return 'https:'; + } else { + return 'http:'; + } +} export const polymorphAdapterSpec = { code: BIDDER_CODE, @@ -17,7 +27,7 @@ export const polymorphAdapterSpec = { * @return boolean True if this is a valid bid, and false otherwise. */ isBidRequestValid: function(bid) { - return !!(bid.params.placementId); + return !!(bid.params.placementId) || (!!(bid.params.network_key) && !!(bid.params.widget_id) && !!(bid.params.cat)); }, /** @@ -31,11 +41,18 @@ export const polymorphAdapterSpec = { var payload = { url: utils.getTopWindowUrl(), ref: utils.getTopFrameReferrer(), - zid: bid.params.placementId, sizes: bid.sizes, hb: 1, - hb_source: 'prebid' + hb_source: 'prebid', + bid_id: bid.bidId, }; + if (bid.params.placementId) { + payload.zid = bid.params.placementId; + } else if (bid.params.network_key && bid.params.widget_id && bid.params.cat) { + payload.network_key = bid.params.network_key; + payload.widget_id = bid.params.widget_id; + payload.cat = bid.params.cat; + } Object.keys(bid.params).forEach(function(key) { if (key != 'defaultWidth' && key != 'defaultHeight') { payload[key] = bid.params[key]; @@ -100,6 +117,14 @@ export const polymorphAdapterSpec = { utils.logError(e); } return bidResponses; + }, + getUserSyncs: function(syncOptions) { + if (syncOptions.pixelEnabled) { + return [{ + type: 'image', + url: USER_SYNC_URL + }]; + } } } diff --git a/modules/prebidServerBidAdapter/config.js b/modules/prebidServerBidAdapter/config.js index 74eb4a95744..835d09e2010 100644 --- a/modules/prebidServerBidAdapter/config.js +++ b/modules/prebidServerBidAdapter/config.js @@ -2,7 +2,6 @@ export const S2S_VENDORS = { 'appnexus': { adapter: 'prebidServer', - cookieSet: false, enabled: true, endpoint: '//prebid.adnxs.com/pbs/v1/openrtb2/auction', syncEndpoint: '//prebid.adnxs.com/pbs/v1/cookie_sync', @@ -10,7 +9,6 @@ export const S2S_VENDORS = { }, 'rubicon': { adapter: 'prebidServer', - cookieSet: false, enabled: true, endpoint: '//prebid-server.rubiconproject.com/openrtb2/auction', syncEndpoint: '//prebid-server.rubiconproject.com/cookie_sync', diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index 5d23a0366ea..80724630da9 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -3,7 +3,6 @@ import bidfactory from 'src/bidfactory'; import * as utils from 'src/utils'; import { ajax } from 'src/ajax'; import { STATUS, S2S, EVENTS } from 'src/constants'; -import { cookieSet } from 'src/cookie.js'; import adaptermanager from 'src/adaptermanager'; import { config } from 'src/config'; import { VIDEO } from 'src/mediaTypes'; @@ -44,7 +43,6 @@ config.setDefaults({ * @property {boolean} [cacheMarkup] whether to cache the adm result * @property {string} [adapter] adapter code to use for S2S * @property {string} [syncEndpoint] endpoint URL for syncing cookies - * @property {string} [cookieSetUrl] url for cookie set library, if passed then cookieSet is enabled */ function setS2sConfig(options) { if (options.defaultVendor) { @@ -355,10 +353,8 @@ const LEGACY_PROTOCOL = { * Protocol spec for OpenRTB endpoint * e.g., https://