From bba4b023290423855fd379fbdcf29d65544401e6 Mon Sep 17 00:00:00 2001 From: smartclip-adtech <65160328+smartclip-adtech@users.noreply.github.com> Date: Wed, 2 Sep 2020 19:13:16 +0200 Subject: [PATCH 01/34] smartxBidAdapter.js - removed unused variables, removed debug, added window before the outstream related functions (#5689) Co-authored-by: Gino --- modules/smartxBidAdapter.js | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/modules/smartxBidAdapter.js b/modules/smartxBidAdapter.js index a745c54e39c..4409e4e9dfb 100644 --- a/modules/smartxBidAdapter.js +++ b/modules/smartxBidAdapter.js @@ -331,13 +331,6 @@ function createOutstreamScript(bid) { // const slot = utils.getBidIdParameter('slot', bid.renderer.config.outstream_options); utils.logMessage('[SMARTX][renderer] Handle SmartX outstream renderer'); const elementId = bid.adUnitCode; - // eslint-disable-next-line camelcase - var sc_smartIntxtStart; - // eslint-disable-next-line camelcase - var sc_smartIntxtNoad; - // eslint-disable-next-line camelcase - var sc_smartIntxtEnd; - var SmartPlay; let smartPlayObj = { minAdWidth: 290, maxAdWidth: 900, @@ -348,20 +341,19 @@ function createOutstreamScript(bid) { }, onStartCallback: function (m, n) { try { - sc_smartIntxtStart(n); + window.sc_smartIntxtStart(n); } catch (f) {} }, onCappedCallback: function (m, n) { try { - sc_smartIntxtNoad(n); + window.sc_smartIntxtNoad(n); } catch (f) {} }, onEndCallback: function (m, n) { try { - sc_smartIntxtEnd(n); + window.sc_smartIntxtEnd(n); } catch (f) {} }, - debug: true }; smartPlayObj.adResponse = bid.vastContent; const script = window.document.createElement('script'); @@ -372,7 +364,7 @@ function createOutstreamScript(bid) { var rs = this.readyState; if (rs && rs != 'complete' && rs != 'loaded') return; try { - SmartPlay(elementId, smartPlayObj); + window.SmartPlay(elementId, smartPlayObj); } catch (e) { utils.logError('error caught : ' + e); } From 6842e8ba17cd2c1488c2a08ba585d080f13f99c7 Mon Sep 17 00:00:00 2001 From: Jaimin Panchal Date: Wed, 2 Sep 2020 17:19:01 -0400 Subject: [PATCH 02/34] Prebid 4.6.0 Release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2a0f4077462..fb30a4397be 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.6.0-pre", + "version": "4.6.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From e783c7539e93076530024dd5ee1be4b5262cfc2e Mon Sep 17 00:00:00 2001 From: Jaimin Panchal Date: Wed, 2 Sep 2020 17:31:42 -0400 Subject: [PATCH 03/34] Increment pre version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index fb30a4397be..3fdc3ba4b83 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.6.0", + "version": "4.7.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From deb7d5b7acd724c9ad9d77beb5ec11d94466bb63 Mon Sep 17 00:00:00 2001 From: Ian Flournoy Date: Thu, 3 Sep 2020 17:19:10 -0400 Subject: [PATCH 04/34] parrableIdSystem: Add an optional timezone and timezone offset allow/block filter (#5569) * Add unit coverage for parrableIdSystem getId callback * PBID-14: Pass uspString to Parrable as us_privacy query parameter * PBID-14: Simplify parrableIdSystem us_privacy test * PBID-14: Only send us_privacy to Parrable when a value exists * PBID-11: Read new Parrable compound cookie _parrable_id Migrating from legacy _parrable_eid cookie. The new cookie contains ibaOptout and ccpaOptout status fields * Remove path check from parrableIdSystem url test * PBID-11: Integrate Parrable compound cookie, consolidating old cookies * PBID-11: Update parrableIdSystem requestBids hook test to support compound cookie value * PBID-11: Small refactor to parrableIdSystem spec to support compound cookie * PBID-11: Handle legacy ibaOptout as truthy value when migrating to compound cookie * PBID-11: Add parrableIdSystem spec tests covering migration of legacy cookies * PBID-11: Remove storage documentation from test pages and userId module docs * PBID-11: Remove SUBMODULES_THAT_ALWAYS_REFRESH_ID feature from userId system * PBID-11: Use better serialize implementation for Parrable compound cookie * PBID-11: Update parrableIdSystem interface documentation * Add missing extension to mock xhr import * PBID-11: Try to access eid property only when parrableId object exists * PBID-11: Construct parrableId from legacy cookies in same manner as compound cookie * Use hardcoded expiration date for legacy cookies * PBID-39: Return full parrableId object in decode method * PBID-39: Update all adapters to use parrableId.eid for userId value * PBID-39: Update config for ORTB EIDs to extract parrableId.eid as User UID value * PBID-39: Pass Parrable IBA and CCPA optout status into ORTB EIDs list through UID extensions * PBID-39: Pass a true CCPA optout status to adapters when the EID has been suppressed The userId/eids module will not consider our ID system for inclusion in the EIDs object if our ID value is not a string. Unfortunately when we write our cookie without an EID (in the case where CCPA optout is true) then the deserialized EID value is undefined, to save space in the cookie. So this is a hack that will return an empty string when Prebid is building the EIDs object so that we can still pass our optout status to those that require it to understand why our ID may be missing. * parrableIdSystem: Relocate new unit test from upstream * PBID-39: Fallback to cookie values when backend response is missing components Also handle another missed callback scenario if the response object parses to nothing * PBID-39: Avoid breaking openx bid adapter when renaming our id system * PBID-39: Use array find * Use supported array find method in parrableIdSystem_spec * Restore backwards-compatible parrableId passing to OpenxBidAdapter * PBID-25: Add time zone and offset filtering of impressions to parrableIdSystem * PBID-25: Better group existing getId tests * PBID-25: Add unit tests covering time zone and offset filtering functionality * PBID-25: Remove parrable .only test scope --- modules/parrableIdSystem.js | 49 +++ test/spec/modules/parrableIdSystem_spec.js | 352 ++++++++++++++++----- 2 files changed, 314 insertions(+), 87 deletions(-) diff --git a/modules/parrableIdSystem.js b/modules/parrableIdSystem.js index 75f89fffc14..2d6a2d6d6e5 100644 --- a/modules/parrableIdSystem.js +++ b/modules/parrableIdSystem.js @@ -113,6 +113,51 @@ function migrateLegacyCookies(parrableId) { } } +function shouldFilterImpression(configParams, parrableId) { + const config = configParams.timezoneFilter; + + if (!config) { + return false; + } + + if (parrableId) { + return false; + } + + const offset = (new Date()).getTimezoneOffset() / 60; + const zone = Intl.DateTimeFormat().resolvedOptions().timeZone; + + function isAllowed() { + if (utils.isEmpty(config.allowedZones) && + utils.isEmpty(config.allowedOffsets)) { + return true; + } + if (utils.contains(config.allowedZones, zone)) { + return true; + } + if (utils.contains(config.allowedOffsets, offset)) { + return true; + } + return false; + } + + function isBlocked() { + if (utils.isEmpty(config.blockedZones) && + utils.isEmpty(config.blockedOffsets)) { + return false; + } + if (utils.contains(config.blockedZones, zone)) { + return true; + } + if (utils.contains(config.blockedOffsets, offset)) { + return true; + } + return false; + } + + return !isAllowed() || isBlocked(); +} + function fetchId(configParams) { if (!isValidConfig(configParams)) return; @@ -122,6 +167,10 @@ function fetchId(configParams) { migrateLegacyCookies(parrableId); } + if (shouldFilterImpression(configParams, parrableId)) { + return null; + } + const eid = (parrableId) ? parrableId.eid : null; const refererInfo = getRefererInfo(); const uspString = uspDataHandler.getConsentData(); diff --git a/test/spec/modules/parrableIdSystem_spec.js b/test/spec/modules/parrableIdSystem_spec.js index 7db22af82ab..1cc89240bc3 100644 --- a/test/spec/modules/parrableIdSystem_spec.js +++ b/test/spec/modules/parrableIdSystem_spec.js @@ -75,113 +75,116 @@ function removeParrableCookie() { } describe('Parrable ID System', function() { - describe('parrableIdSystem.getId() callback', function() { - let logErrorStub; - let callbackSpy = sinon.spy(); + describe('parrableIdSystem.getId()', function() { + describe('response callback function', function() { + let logErrorStub; + let callbackSpy = sinon.spy(); + + beforeEach(function() { + logErrorStub = sinon.stub(utils, 'logError'); + callbackSpy.resetHistory(); + writeParrableCookie({ eid: P_COOKIE_EID }); + }); - beforeEach(function() { - logErrorStub = sinon.stub(utils, 'logError'); - callbackSpy.resetHistory(); - writeParrableCookie({ eid: P_COOKIE_EID }); - }); + afterEach(function() { + removeParrableCookie(); + logErrorStub.restore(); + }) - afterEach(function() { - removeParrableCookie(); - logErrorStub.restore(); - }) + it('creates xhr to Parrable that synchronizes the ID', function() { + let getIdResult = parrableIdSubmodule.getId(P_CONFIG_MOCK.params); - it('creates xhr to Parrable that synchronizes the ID', function() { - let getIdResult = parrableIdSubmodule.getId(P_CONFIG_MOCK.params); + getIdResult.callback(callbackSpy); - getIdResult.callback(callbackSpy); + let request = server.requests[0]; + let queryParams = utils.parseQS(request.url.split('?')[1]); + let data = JSON.parse(atob(queryParams.data)); - let request = server.requests[0]; - let queryParams = utils.parseQS(request.url.split('?')[1]); - let data = JSON.parse(atob(queryParams.data)); + expect(getIdResult.callback).to.be.a('function'); + expect(request.url).to.contain('h.parrable.com'); - expect(getIdResult.callback).to.be.a('function'); - expect(request.url).to.contain('h.parrable.com'); + expect(queryParams).to.not.have.property('us_privacy'); + expect(data).to.deep.equal({ + eid: P_COOKIE_EID, + trackers: P_CONFIG_MOCK.params.partner.split(','), + url: getRefererInfo().referer + }); - expect(queryParams).to.not.have.property('us_privacy'); - expect(data).to.deep.equal({ - eid: P_COOKIE_EID, - trackers: P_CONFIG_MOCK.params.partner.split(','), - url: getRefererInfo().referer - }); + server.requests[0].respond(200, + { 'Content-Type': 'text/plain' }, + JSON.stringify({ eid: P_XHR_EID }) + ); - server.requests[0].respond(200, - { 'Content-Type': 'text/plain' }, - JSON.stringify({ eid: P_XHR_EID }) - ); + expect(callbackSpy.lastCall.lastArg).to.deep.equal({ + eid: P_XHR_EID + }); - expect(callbackSpy.lastCall.lastArg).to.deep.equal({ - eid: P_XHR_EID + expect(storage.getCookie(P_COOKIE_NAME)).to.equal( + encodeURIComponent('eid:' + P_XHR_EID) + ); }); - expect(storage.getCookie(P_COOKIE_NAME)).to.equal( - encodeURIComponent('eid:' + P_XHR_EID) - ); - }); - - it('xhr passes the uspString to Parrable', function() { - let uspString = '1YNN'; - uspDataHandler.setConsentData(uspString); - parrableIdSubmodule.getId( - P_CONFIG_MOCK.params, - null, - null - ).callback(callbackSpy); - uspDataHandler.setConsentData(null); - expect(server.requests[0].url).to.contain('us_privacy=' + uspString); - }); + it('xhr passes the uspString to Parrable', function() { + let uspString = '1YNN'; + uspDataHandler.setConsentData(uspString); + parrableIdSubmodule.getId( + P_CONFIG_MOCK.params, + null, + null + ).callback(callbackSpy); + uspDataHandler.setConsentData(null); + expect(server.requests[0].url).to.contain('us_privacy=' + uspString); + }); - it('should log an error and continue to callback if ajax request errors', function () { - let callBackSpy = sinon.spy(); - let submoduleCallback = parrableIdSubmodule.getId({partner: 'prebid'}).callback; - submoduleCallback(callBackSpy); - let request = server.requests[0]; - expect(request.url).to.contain('h.parrable.com'); - request.respond( - 503, - null, - 'Unavailable' - ); - expect(logErrorStub.calledOnce).to.be.true; - expect(callBackSpy.calledOnce).to.be.true; + it('should log an error and continue to callback if ajax request errors', function () { + let callBackSpy = sinon.spy(); + let submoduleCallback = parrableIdSubmodule.getId({partner: 'prebid'}).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.contain('h.parrable.com'); + request.respond( + 503, + null, + 'Unavailable' + ); + expect(logErrorStub.calledOnce).to.be.true; + expect(callBackSpy.calledOnce).to.be.true; + }); }); - }); - describe('parrableIdSystem.getId() id', function() { - it('provides the stored Parrable values if a cookie exists', function() { - writeParrableCookie({ eid: P_COOKIE_EID }); - let getIdResult = parrableIdSubmodule.getId(P_CONFIG_MOCK.params); - removeParrableCookie(); + describe('response id', function() { + it('provides the stored Parrable values if a cookie exists', function() { + writeParrableCookie({ eid: P_COOKIE_EID }); + let getIdResult = parrableIdSubmodule.getId(P_CONFIG_MOCK.params); + removeParrableCookie(); - expect(getIdResult.id).to.deep.equal({ - eid: P_COOKIE_EID + expect(getIdResult.id).to.deep.equal({ + eid: P_COOKIE_EID + }); }); - }); - it('provides the stored legacy Parrable ID values if cookies exist', function() { - let oldEid = '01.111.old-eid'; - let oldEidCookieName = '_parrable_eid'; - let oldOptoutCookieName = '_parrable_optout'; + it('provides the stored legacy Parrable ID values if cookies exist', function() { + let oldEid = '01.111.old-eid'; + let oldEidCookieName = '_parrable_eid'; + let oldOptoutCookieName = '_parrable_optout'; - storage.setCookie(oldEidCookieName, oldEid); - storage.setCookie(oldOptoutCookieName, 'true'); + storage.setCookie(oldEidCookieName, oldEid); + storage.setCookie(oldOptoutCookieName, 'true'); - let getIdResult = parrableIdSubmodule.getId(P_CONFIG_MOCK.params); - expect(getIdResult.id).to.deep.equal({ - eid: oldEid, - ibaOptout: true - }); + let getIdResult = parrableIdSubmodule.getId(P_CONFIG_MOCK.params); + expect(getIdResult.id).to.deep.equal({ + eid: oldEid, + ibaOptout: true + }); - // The ID system is expected to migrate old cookies to the new format - expect(storage.getCookie(P_COOKIE_NAME)).to.equal( - encodeURIComponent('eid:' + oldEid + ',ibaOptout:1') - ); - expect(storage.getCookie(oldEidCookieName)).to.equal(null); - expect(storage.getCookie(oldOptoutCookieName)).to.equal(null); + // The ID system is expected to migrate old cookies to the new format + expect(storage.getCookie(P_COOKIE_NAME)).to.equal( + encodeURIComponent('eid:' + oldEid + ',ibaOptout:1') + ); + expect(storage.getCookie(oldEidCookieName)).to.equal(null); + expect(storage.getCookie(oldOptoutCookieName)).to.equal(null); + removeParrableCookie(); + }); }); }); @@ -199,6 +202,181 @@ describe('Parrable ID System', function() { }); }); + describe('timezone filtering', function() { + before(function() { + sinon.stub(Intl, 'DateTimeFormat'); + }); + + after(function() { + Intl.DateTimeFormat.restore(); + }); + + it('permits an impression when no timezoneFilter is configured', function() { + expect(parrableIdSubmodule.getId({ + partner: 'prebid-test', + })).to.have.property('callback'); + }); + + it('permits an impression from a blocked timezone when a cookie exists', function() { + const blockedZone = 'Antarctica/South_Pole'; + const resolvedOptions = sinon.stub().returns({ timeZone: blockedZone }); + Intl.DateTimeFormat.returns({ resolvedOptions }); + + writeParrableCookie({ eid: P_COOKIE_EID }); + + expect(parrableIdSubmodule.getId({ + partner: 'prebid-test', + timezoneFilter: { + blockedZones: [ blockedZone ] + } + })).to.have.property('callback'); + expect(resolvedOptions.called).to.equal(false); + + removeParrableCookie(); + }) + + it('permits an impression from an allowed timezone', function() { + const allowedZone = 'America/New_York'; + const resolvedOptions = sinon.stub().returns({ timeZone: allowedZone }); + Intl.DateTimeFormat.returns({ resolvedOptions }); + + expect(parrableIdSubmodule.getId({ + partner: 'prebid-test', + timezoneFilter: { + allowedZones: [ allowedZone ] + } + })).to.have.property('callback'); + expect(resolvedOptions.called).to.equal(true); + }); + + it('permits an impression from a timezone that is not blocked', function() { + const blockedZone = 'America/New_York'; + const resolvedOptions = sinon.stub().returns({ timeZone: 'Iceland' }); + Intl.DateTimeFormat.returns({ resolvedOptions }); + + expect(parrableIdSubmodule.getId({ + partner: 'prebid-test', + timezoneFilter: { + blockedZones: [ blockedZone ] + } + })).to.have.property('callback'); + expect(resolvedOptions.called).to.equal(true); + }); + + it('does not permit an impression from a blocked timezone', function() { + const blockedZone = 'America/New_York'; + const resolvedOptions = sinon.stub().returns({ timeZone: blockedZone }); + Intl.DateTimeFormat.returns({ resolvedOptions }); + + expect(parrableIdSubmodule.getId({ + partner: 'prebid-test', + timezoneFilter: { + blockedZones: [ blockedZone ] + } + })).to.equal(null); + expect(resolvedOptions.called).to.equal(true); + }); + + it('does not permit an impression from a blocked timezone even when also allowed', function() { + const timezone = 'America/New_York'; + const resolvedOptions = sinon.stub().returns({ timeZone: timezone }); + Intl.DateTimeFormat.returns({ resolvedOptions }); + + expect(parrableIdSubmodule.getId({ + partner: 'prebid-test', + timezoneFilter: { + allowedZones: [ timezone ], + blockedZones: [ timezone ] + } + })).to.equal(null); + expect(resolvedOptions.called).to.equal(true); + }); + }); + + describe('timezone offset filtering', function() { + before(function() { + sinon.stub(Date.prototype, 'getTimezoneOffset'); + }); + + afterEach(function() { + Date.prototype.getTimezoneOffset.reset(); + }) + + after(function() { + Date.prototype.getTimezoneOffset.restore(); + }); + + it('permits an impression from a blocked offset when a cookie exists', function() { + const blockedOffset = -4; + Date.prototype.getTimezoneOffset.returns(blockedOffset * 60); + + writeParrableCookie({ eid: P_COOKIE_EID }); + + expect(parrableIdSubmodule.getId({ + partner: 'prebid-test', + timezoneFilter: { + blockedOffsets: [ blockedOffset ] + } + })).to.have.property('callback'); + + removeParrableCookie(); + }); + + it('permits an impression from an allowed offset', function() { + const allowedOffset = -5; + Date.prototype.getTimezoneOffset.returns(allowedOffset * 60); + + expect(parrableIdSubmodule.getId({ + partner: 'prebid-test', + timezoneFilter: { + allowedOffsets: [ allowedOffset ] + } + })).to.have.property('callback'); + expect(Date.prototype.getTimezoneOffset.called).to.equal(true); + }); + + it('permits an impression from an offset that is not blocked', function() { + const allowedOffset = -5; + const blockedOffset = 5; + Date.prototype.getTimezoneOffset.returns(allowedOffset * 60); + + expect(parrableIdSubmodule.getId({ + partner: 'prebid-test', + timezoneFilter: { + blockedOffsets: [ blockedOffset ] + } + })).to.have.property('callback'); + expect(Date.prototype.getTimezoneOffset.called).to.equal(true); + }); + + it('does not permit an impression from a blocked offset', function() { + const blockedOffset = -5; + Date.prototype.getTimezoneOffset.returns(blockedOffset * 60); + + expect(parrableIdSubmodule.getId({ + partner: 'prebid-test', + timezoneFilter: { + blockedOffsets: [ blockedOffset ] + } + })).to.equal(null); + expect(Date.prototype.getTimezoneOffset.called).to.equal(true); + }); + + it('does not permit an impression from a blocked offset even when also allowed', function() { + const offset = -5; + Date.prototype.getTimezoneOffset.returns(offset * 60); + + expect(parrableIdSubmodule.getId({ + partner: 'prebid-test', + timezoneFilter: { + allowedOffset: [ offset ], + blockedOffsets: [ offset ] + } + })).to.equal(null); + expect(Date.prototype.getTimezoneOffset.called).to.equal(true); + }); + }); + describe('userId requestBids hook', function() { let adUnits; From 7743713c508529e49453371897a713728cbcf81a Mon Sep 17 00:00:00 2001 From: Klaas-Jan Boon Date: Fri, 4 Sep 2020 22:09:26 +0200 Subject: [PATCH 05/34] Blue Billywig bid adapter update (#5584) * add Blue Billywig adapter * Blue Billywig Adapter - update according to review feedback * Blue Billywig Adapter - update to try and pass CircleCI * Remove the last for .. of in bluebillywigBidAdapter.js, hopefully... * Code quality update, always hit user syncs, improved video params Co-authored-by: Klaas-Jan Boon Co-authored-by: Klaas-Jan Boon --- modules/bluebillywigBidAdapter.js | 189 ++++++++---------- .../modules/bluebillywigBidAdapter_spec.js | 177 +++++++++++++++- 2 files changed, 250 insertions(+), 116 deletions(-) diff --git a/modules/bluebillywigBidAdapter.js b/modules/bluebillywigBidAdapter.js index 4d40d931e1d..afacc48aa8e 100644 --- a/modules/bluebillywigBidAdapter.js +++ b/modules/bluebillywigBidAdapter.js @@ -1,4 +1,5 @@ import * as utils from '../src/utils.js'; +import find from 'core-js-pure/features/array/find.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; @@ -17,7 +18,10 @@ const BB_CONSTANTS = { DEFAULT_TTL: 300, DEFAULT_WIDTH: 768, DEFAULT_HEIGHT: 432, - DEFAULT_NET_REVENUE: true + DEFAULT_NET_REVENUE: true, + VIDEO_PARAMS: ['mimes', 'minduration', 'maxduration', 'protocols', 'w', 'h', 'startdelay', 'placement', 'linearity', 'skip', 'skipmin', + 'skipafter', 'sequence', 'battr', 'maxextended', 'minbitrate', 'maxbitrate', 'boxingallowed', 'playbackmethod', 'playbackend', 'delivery', 'pos', 'companionad', + 'api', 'companiontype', 'ext'] }; // Aliasing @@ -26,8 +30,6 @@ const getConfig = config.getConfig; // Helper Functions export const BB_HELPERS = { addSiteAppDevice: function(request, pageUrl) { - if (!request) return; - if (typeof getConfig('app') === 'object') request.app = getConfig('app'); else { request.site = {}; @@ -41,21 +43,15 @@ export const BB_HELPERS = { if (!request.device.h) request.device.h = window.innerHeight; }, addSchain: function(request, validBidRequests) { - if (!request) return; - const schain = utils.deepAccess(validBidRequests, '0.schain'); if (schain) request.source.ext = { schain: schain }; }, addCurrency: function(request) { - if (!request) return; - const adServerCur = getConfig('currency.adServerCurrency'); if (adServerCur && typeof adServerCur === 'string') request.cur = [adServerCur]; else if (Array.isArray(adServerCur) && adServerCur.length) request.cur = [adServerCur[0]]; }, addUserIds: function(request, validBidRequests) { - if (!request) return; - const bidUserId = utils.deepAccess(validBidRequests, '0.userId'); const eids = createEidsArray(bidUserId); @@ -63,10 +59,6 @@ export const BB_HELPERS = { utils.deepSetValue(request, 'user.ext.eids', eids); } }, - addDigiTrust: function(request, bidRequests) { - const digiTrust = BB_HELPERS.getDigiTrustParams(bidRequests && bidRequests[0]); - if (digiTrust) utils.deepSetValue(request, 'user.ext.digitrust', digiTrust); - }, substituteUrl: function (url, publication, renderer) { return url.replace('$$URL_START', (DEV_MODE) ? 'https://dev.' : 'https://').replace('$$PUBLICATION', publication).replace('$$RENDERER', renderer); }, @@ -79,41 +71,59 @@ export const BB_HELPERS = { getRendererUrl: function(publication, renderer) { return BB_HELPERS.substituteUrl(BB_CONSTANTS.RENDERER_URL, publication, renderer); }, - getDigiTrustParams: function(bidRequest) { - const digiTrustId = BB_HELPERS.getDigiTrustId(bidRequest); + transformVideoParams: function(videoParams, videoParamsExt) { + videoParams = utils.deepClone(videoParams); - if (!digiTrustId || (digiTrustId.privacy && digiTrustId.privacy.optout)) return null; - return { - id: digiTrustId.id, - keyv: digiTrustId.keyv - } - }, - getDigiTrustId: function(bidRequest) { - const bidRequestDigiTrust = utils.deepAccess(bidRequest, 'userId.digitrustid.data'); - if (bidRequestDigiTrust) return bidRequestDigiTrust; + let playerSize = videoParams.playerSize || [BB_CONSTANTS.DEFAULT_WIDTH, BB_CONSTANTS.DEFAULT_HEIGHT]; + if (Array.isArray(playerSize[0])) playerSize = playerSize[0]; + + videoParams.w = playerSize[0]; + videoParams.h = playerSize[1]; + videoParams.placement = 3; + + if (videoParamsExt) videoParams = Object.assign(videoParams, videoParamsExt); + + const videoParamsProperties = Object.keys(videoParams); - const digiTrustUser = getConfig('digiTrustId'); - return (digiTrustUser && digiTrustUser.success && digiTrustUser.identity) || null; + videoParamsProperties.forEach(property => { + if (BB_CONSTANTS.VIDEO_PARAMS.indexOf(property) === -1) delete videoParams[property]; + }); + + return videoParams; }, transformRTBToPrebidProps: function(bid, serverResponse) { - bid.cpm = bid.price; delete bid.price; - bid.bidId = bid.impid; - bid.requestId = bid.impid; delete bid.impid; - bid.width = bid.w || BB_CONSTANTS.DEFAULT_WIDTH; - bid.height = bid.h || BB_CONSTANTS.DEFAULT_HEIGHT; + const bidObject = { + cpm: bid.price, + currency: serverResponse.cur, + netRevenue: BB_CONSTANTS.DEFAULT_NET_REVENUE, + bidId: bid.impid, + requestId: bid.impid, + creativeId: bid.crid, + mediaType: VIDEO, + width: bid.w || BB_CONSTANTS.DEFAULT_WIDTH, + height: bid.h || BB_CONSTANTS.DEFAULT_HEIGHT, + ttl: BB_CONSTANTS.DEFAULT_TTL + }; + + const extPrebidTargeting = utils.deepAccess(bid, 'ext.prebid.targeting'); + const extPrebidCache = utils.deepAccess(bid, 'ext.prebid.cache'); + + if (extPrebidCache && typeof extPrebidCache.vastXml === 'object' && extPrebidCache.vastXml.cacheId && extPrebidCache.vastXml.url) { + bidObject.videoCacheKey = extPrebidCache.vastXml.cacheId; + bidObject.vastUrl = extPrebidCache.vastXml.url; + } else if (extPrebidTargeting && extPrebidTargeting.hb_uuid && extPrebidTargeting.hb_cache_host && extPrebidTargeting.hb_cache_path) { + bidObject.videoCacheKey = extPrebidTargeting.hb_uuid; + bidObject.vastUrl = `https://${extPrebidTargeting.hb_cache_host}${extPrebidTargeting.hb_cache_path}?uuid=${extPrebidTargeting.hb_uuid}`; + } if (bid.adm) { - bid.ad = bid.adm; - bid.vastXml = bid.adm; - delete bid.adm; + bidObject.ad = bid.adm; + bidObject.vastXml = bid.adm; } - if (bid.nurl && !bid.adm) { // ad markup is on win notice url, and adm is ommited according to OpenRTB 2.5 - bid.vastUrl = bid.nurl; - delete bid.nurl; + if (!bidObject.vastUrl && bid.nurl && !bid.adm) { // ad markup is on win notice url, and adm is ommited according to OpenRTB 2.5 + bidObject.vastUrl = bid.nurl; } - bid.netRevenue = BB_CONSTANTS.DEFAULT_NET_REVENUE; - bid.creativeId = bid.crid; delete bid.crid; - bid.currency = serverResponse.cur; - bid.ttl = BB_CONSTANTS.DEFAULT_TTL; + + return bidObject; }, }; @@ -132,18 +142,14 @@ const BB_RENDERER = { return; } - const rendererId = BB_RENDERER.getRendererId(bid.publicationName, bid.rendererCode); + if (!(window.bluebillywig && window.bluebillywig.renderers)) { + utils.logWarn(`${BB_CONSTANTS.BIDDER_CODE}: renderer code failed to initialize...`); + return; + } + const rendererId = BB_RENDERER.getRendererId(bid.publicationName, bid.rendererCode); const ele = document.getElementById(bid.adUnitCode); // NB convention - - let renderer; - - for (let rendererIndex = 0; rendererIndex < window.bluebillywig.renderers.length; rendererIndex++) { - if (window.bluebillywig.renderers[rendererIndex]._id === rendererId) { - renderer = window.bluebillywig.renderers[rendererIndex]; - break; - } - } + const renderer = find(window.bluebillywig.renderers, r => r._id === rendererId); if (renderer) renderer.bootstrap(config, ele); else utils.logWarn(`${BB_CONSTANTS.BIDDER_CODE}: Couldn't find a renderer with ${rendererId}`); @@ -212,9 +218,8 @@ export const spec = { utils.logError(`${BB_CONSTANTS.BIDDER_CODE}: connections is not of type array. Rejecting bid: `, bid); return false; } else { - for (let connectionIndex = 0; connectionIndex < bid.params.connections.length; connectionIndex++) { - const connection = bid.params.connections[connectionIndex]; - if (!bid.params.hasOwnProperty(connection)) { + for (let i = 0; i < bid.params.connections.length; i++) { + if (!bid.params.hasOwnProperty(bid.params.connections[i])) { utils.logError(`${BB_CONSTANTS.BIDDER_CODE}: connection specified in params.connections, but not configured in params. Rejecting bid: `, bid); return false; } @@ -225,6 +230,11 @@ export const spec = { return false; } + if (bid.params.hasOwnProperty('video') && (bid.params.video === null || typeof bid.params.video !== 'object')) { + utils.logError(`${BB_CONSTANTS.BIDDER_CODE}: params.video must be of type object. Rejecting bid: `, bid); + return false; + } + if (bid.hasOwnProperty('mediaTypes') && bid.mediaTypes.hasOwnProperty(VIDEO)) { if (!bid.mediaTypes[VIDEO].hasOwnProperty('context')) { utils.logError(`${BB_CONSTANTS.BIDDER_CODE}: no context specified in bid. Rejecting bid: `, bid); @@ -245,20 +255,21 @@ export const spec = { buildRequests(validBidRequests, bidderRequest) { const imps = []; - for (let validBidRequestIndex = 0; validBidRequestIndex < validBidRequests.length; validBidRequestIndex++) { - const validBidRequest = validBidRequests[validBidRequestIndex]; - const _this = this; + validBidRequests.forEach(validBidRequest => { + if (!this.syncStore.publicationName) this.syncStore.publicationName = validBidRequest.params.publicationName; + if (!this.syncStore.accountId) this.syncStore.accountId = validBidRequest.params.accountId; - const ext = validBidRequest.params.connections.reduce(function(extBuilder, connection) { + const ext = validBidRequest.params.connections.reduce((extBuilder, connection) => { extBuilder[connection] = validBidRequest.params[connection]; - if (_this.syncStore.bidders.indexOf(connection) === -1) _this.syncStore.bidders.push(connection); + if (this.syncStore.bidders.indexOf(connection) === -1) this.syncStore.bidders.push(connection); return extBuilder; }, {}); - imps.push({ id: validBidRequest.bidId, ext, secure: window.location.protocol === 'https' ? 1 : 0, video: utils.deepAccess(validBidRequest, 'mediaTypes.video') }); - } + const videoParams = BB_HELPERS.transformVideoParams(utils.deepAccess(validBidRequest, 'mediaTypes.video'), utils.deepAccess(validBidRequest, 'params.video')); + imps.push({ id: validBidRequest.bidId, ext, secure: window.location.protocol === 'https' ? 1 : 0, video: videoParams }); + }); const request = { id: bidderRequest.auctionId, @@ -293,7 +304,6 @@ export const spec = { BB_HELPERS.addSchain(request, validBidRequests); BB_HELPERS.addCurrency(request); BB_HELPERS.addUserIds(request, validBidRequests); - BB_HELPERS.addDigiTrust(request, validBidRequests); return { method: 'POST', @@ -311,74 +321,43 @@ export const spec = { const bids = []; - for (let seatbidIndex = 0; seatbidIndex < serverResponse.seatbid.length; seatbidIndex++) { - const seatbid = serverResponse.seatbid[seatbidIndex]; - if (!seatbid.bid || !Array.isArray(seatbid.bid)) continue; - for (let bidIndex = 0; bidIndex < seatbid.bid.length; bidIndex++) { - const bid = seatbid.bid[bidIndex]; - BB_HELPERS.transformRTBToPrebidProps(bid, serverResponse); - - let bidParams; - for (let bidderRequestBidsIndex = 0; bidderRequestBidsIndex < request.bidderRequest.bids.length; bidderRequestBidsIndex++) { - if (request.bidderRequest.bids[bidderRequestBidsIndex].bidId === bid.bidId) { - bidParams = request.bidderRequest.bids[bidderRequestBidsIndex].params; - } - } + serverResponse.seatbid.forEach(seatbid => { + if (!seatbid.bid || !Array.isArray(seatbid.bid)) return; + seatbid.bid.forEach(bid => { + bid = BB_HELPERS.transformRTBToPrebidProps(bid, serverResponse); - if (bidParams) { - bid.publicationName = bidParams.publicationName; - bid.rendererCode = bidParams.rendererCode; - bid.accountId = bidParams.accountId; - } + const bidParams = find(request.bidderRequest.bids, bidderRequestBid => bidderRequestBid.bidId === bid.bidId).params; + bid.publicationName = bidParams.publicationName; + bid.rendererCode = bidParams.rendererCode; + bid.accountId = bidParams.accountId; const rendererUrl = BB_HELPERS.getRendererUrl(bid.publicationName, bid.rendererCode); - bid.renderer = BB_RENDERER.newRenderer(rendererUrl, bid.adUnitCode); bids.push(bid); - } - } + }); + }); return bids; }, getUserSyncs(syncOptions, serverResponses, gdpr) { - if (!serverResponses || !serverResponses.length) return []; if (!syncOptions.iframeEnabled) return []; const queryString = []; - let accountId; - let publication; - - const serverResponse = serverResponses[0]; - if (!serverResponse.body || !serverResponse.body.seatbid) return []; - - for (let seatbidIndex = 0; seatbidIndex < serverResponse.body.seatbid.length; seatbidIndex++) { - const seatbid = serverResponse.body.seatbid[seatbidIndex]; - for (let bidIndex = 0; bidIndex < seatbid.bid.length; bidIndex++) { - const bid = seatbid.bid[bidIndex]; - accountId = bid.accountId || null; - publication = bid.publicationName || null; - - if (publication && accountId) break; - } - if (publication && accountId) break; - } - - if (!publication || !accountId) return []; if (gdpr.gdprApplies) queryString.push(`gdpr=${gdpr.gdprApplies ? 1 : 0}`); if (gdpr.gdprApplies && gdpr.consentString) queryString.push(`gdpr_consent=${gdpr.consentString}`); if (this.syncStore.uspConsent) queryString.push(`usp_consent=${this.syncStore.uspConsent}`); - queryString.push(`accountId=${accountId}`); + queryString.push(`accountId=${this.syncStore.accountId}`); queryString.push(`bidders=${btoa(JSON.stringify(this.syncStore.bidders))}`); queryString.push(`cb=${Date.now()}-${Math.random().toString().replace('.', '')}`); if (DEV_MODE) queryString.push('bbpbs_debug=true'); // NB syncUrl by default starts with ?pub=$$PUBLICATION - const syncUrl = `${BB_HELPERS.getSyncUrl(publication)}&${queryString.join('&')}`; + const syncUrl = `${BB_HELPERS.getSyncUrl(this.syncStore.publicationName)}&${queryString.join('&')}`; return [{ type: 'iframe', diff --git a/test/spec/modules/bluebillywigBidAdapter_spec.js b/test/spec/modules/bluebillywigBidAdapter_spec.js index 73bd4803358..56ea29d6d73 100644 --- a/test/spec/modules/bluebillywigBidAdapter_spec.js +++ b/test/spec/modules/bluebillywigBidAdapter_spec.js @@ -40,6 +40,13 @@ describe('BlueBillywigAdapter', () => { expect(spec.isBidRequestValid(baseValidBid)).to.equal(true); }); + it('should return false when params missing', () => { + const bid = deepClone(baseValidBid); + delete bid.params; + + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + it('should return false when publicationName is missing', () => { const bid = deepClone(baseValidBid); delete bid.params.publicationName; @@ -184,6 +191,25 @@ describe('BlueBillywigAdapter', () => { bid.mediaTypes[VIDEO].context = 'instream'; expect(spec.isBidRequestValid(bid)).to.equal(false); }); + + it('should fail if video is specified but is not an object', () => { + const bid = deepClone(baseValidBid); + + bid.params.video = null; + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.video = 'string'; + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.video = 123; + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.video = false; + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.video = void (0); + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); }); describe('buildRequests', () => { @@ -510,30 +536,64 @@ describe('BlueBillywigAdapter', () => { expect(deepAccess(payload, 'user.ext.eids')).to.be.undefined; }); - it('should set digitrust when present on bid', () => { - const digiTrust = {data: {id: 'DTID', keyv: 4, privacy: {optout: false}, producer: 'ABC', version: 2}}; + it('should set imp.0.video.[w|h|placement] by default', () => { + const newBaseValidBidRequests = deepClone(baseValidBidRequests); + + const request = spec.buildRequests(newBaseValidBidRequests, validBidderRequest); + const payload = JSON.parse(request.data); + + expect(deepAccess(payload, 'imp.0.video.w')).to.equal(768); + expect(deepAccess(payload, 'imp.0.video.h')).to.equal(432); + expect(deepAccess(payload, 'imp.0.video.placement')).to.equal(3); + }); + it('should update imp0.video.[w|h] when present in config', () => { const newBaseValidBidRequests = deepClone(baseValidBidRequests); - newBaseValidBidRequests[0].userId = { digitrustid: digiTrust }; + newBaseValidBidRequests[0].mediaTypes.video.playerSize = [1, 1]; const request = spec.buildRequests(newBaseValidBidRequests, validBidderRequest); const payload = JSON.parse(request.data); - expect(payload).to.have.nested.property('user.ext.digitrust'); - expect(payload.user.ext.digitrust.id).to.equal(digiTrust.data.id); - expect(payload.user.ext.digitrust.keyv).to.equal(digiTrust.data.keyv); + expect(deepAccess(payload, 'imp.0.video.w')).to.equal(1); + expect(deepAccess(payload, 'imp.0.video.h')).to.equal(1); }); - it('should not set digitrust when opted out', () => { - const digiTrust = {data: {id: 'DTID', keyv: 4, privacy: {optout: true}, producer: 'ABC', version: 2}}; + it('should allow overriding any imp0.video key through params.video', () => { + const newBaseValidBidRequests = deepClone(baseValidBidRequests); + newBaseValidBidRequests[0].params.video = { + w: 2, + h: 2, + placement: 1, + minduration: 15, + maxduration: 30 + }; + + const request = spec.buildRequests(newBaseValidBidRequests, validBidderRequest); + const payload = JSON.parse(request.data); + expect(deepAccess(payload, 'imp.0.video.w')).to.equal(2); + expect(deepAccess(payload, 'imp.0.video.h')).to.equal(2); + expect(deepAccess(payload, 'imp.0.video.placement')).to.equal(1); + expect(deepAccess(payload, 'imp.0.video.minduration')).to.equal(15); + expect(deepAccess(payload, 'imp.0.video.maxduration')).to.equal(30); + }); + + it('should not allow placing any non-OpenRTB 2.5 keys on imp.0.video through params.video', () => { const newBaseValidBidRequests = deepClone(baseValidBidRequests); - newBaseValidBidRequests[0].userId = { digitrustid: digiTrust }; + newBaseValidBidRequests[0].params.video = { + 'true': true, + 'testing': 'some', + 123: {}, + '': 'values' + }; const request = spec.buildRequests(newBaseValidBidRequests, validBidderRequest); const payload = JSON.parse(request.data); - expect(deepAccess(payload, 'user.ext.digitrust')).to.be.undefined; + expect(deepAccess(request, 'imp.0.video.true')).to.be.undefined; + expect(deepAccess(payload, 'imp.0.video.testing')).to.be.undefined; + expect(deepAccess(payload, 'imp.0.video.123')).to.be.undefined; + expect(deepAccess(payload, 'imp.0.video.')).to.be.undefined; }); }); describe('interpretResponse', () => { @@ -571,7 +631,7 @@ describe('BlueBillywigAdapter', () => { bidder: BB_CONSTANTS.BIDDER_CODE, bidderRequestId: '1a2345b67c8d9e0', params: baseValidBid.params, - sizes: [[768, 432], [640, 480], [630, 360]], + sizes: [[640, 480], [630, 360]], transactionId: '2b34c5de-f67a-8901-bcd2-34567efabc89' }], start: 11585918458869, @@ -759,6 +819,101 @@ describe('BlueBillywigAdapter', () => { expect(result.length).to.equal(0); } }); + + it('should take default width and height when w/h not present', () => { + const bidSizesMissing = deepClone(serverResponse); + + delete bidSizesMissing.body.seatbid[0].bid[0].w; + delete bidSizesMissing.body.seatbid[0].bid[0].h; + + const response = bidSizesMissing; + const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); + const result = spec.interpretResponse(response, request); + + expect(deepAccess(result, '0.width')).to.equal(768); + expect(deepAccess(result, '0.height')).to.equal(432); + }); + + it('should take nurl value when adm not present', () => { + const bidAdmMissing = deepClone(serverResponse); + + delete bidAdmMissing.body.seatbid[0].bid[0].adm; + bidAdmMissing.body.seatbid[0].bid[0].nurl = 'https://bluebillywig.com'; + + const response = bidAdmMissing; + const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); + const result = spec.interpretResponse(response, request); + + expect(deepAccess(result, '0.vastXml')).to.be.undefined; + expect(deepAccess(result, '0.vastUrl')).to.equal('https://bluebillywig.com'); + }); + + it('should not take nurl value when adm present', () => { + const bidAdmNurlPresent = deepClone(serverResponse); + + bidAdmNurlPresent.body.seatbid[0].bid[0].nurl = 'https://bluebillywig.com'; + + const response = bidAdmNurlPresent; + const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); + const result = spec.interpretResponse(response, request); + + expect(deepAccess(result, '0.vastXml')).to.equal(bidAdmNurlPresent.body.seatbid[0].bid[0].adm); + expect(deepAccess(result, '0.vastUrl')).to.be.undefined; + }); + + it('should take ext.prebid.cache data when present, ignore ext.prebid.targeting and nurl', () => { + const bidExtPrebidCache = deepClone(serverResponse); + + delete bidExtPrebidCache.body.seatbid[0].bid[0].adm; + bidExtPrebidCache.body.seatbid[0].bid[0].nurl = 'https://notnurl.com'; + + bidExtPrebidCache.body.seatbid[0].bid[0].ext = { + prebid: { + cache: { + vastXml: { + url: 'https://bluebillywig.com', + cacheId: '12345' + } + }, + targeting: { + hb_uuid: '23456', + hb_cache_host: 'bluebillywig.com', + hb_cache_path: '/cache' + } + } + }; + + const response = bidExtPrebidCache; + const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); + const result = spec.interpretResponse(response, request); + + expect(deepAccess(result, '0.vastUrl')).to.equal('https://bluebillywig.com'); + expect(deepAccess(result, '0.videoCacheKey')).to.equal('12345'); + }); + + it('should take ext.prebid.targeting data when ext.prebid.cache not present, and ignore nurl', () => { + const bidExtPrebidTargeting = deepClone(serverResponse); + + delete bidExtPrebidTargeting.body.seatbid[0].bid[0].adm; + bidExtPrebidTargeting.body.seatbid[0].bid[0].nurl = 'https://notnurl.com'; + + bidExtPrebidTargeting.body.seatbid[0].bid[0].ext = { + prebid: { + targeting: { + hb_uuid: '34567', + hb_cache_host: 'bluebillywig.com', + hb_cache_path: '/cache' + } + } + }; + + const response = bidExtPrebidTargeting; + const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); + const result = spec.interpretResponse(response, request); + + expect(deepAccess(result, '0.vastUrl')).to.equal('https://bluebillywig.com/cache?uuid=34567'); + expect(deepAccess(result, '0.videoCacheKey')).to.equal('34567'); + }); }); describe('getUserSyncs', () => { const publicationName = 'bbprebid.dev'; From 9626398920021cf2b745e0998b73147c746bc618 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Udi=20Talias=20=E2=9A=9B=EF=B8=8F?= Date: Sat, 5 Sep 2020 18:53:25 +0300 Subject: [PATCH 06/34] Vidazoo Adapter: refactor/user-sync (#5654) * feat(module): multi size request * fix getUserSyncs added tests * update(module): package-lock.json from master * feat(client): initial refactor commit * fix(client): lint issues Co-authored-by: roman --- modules/vidazooBidAdapter.js | 45 +++++---------------- test/spec/modules/vidazooBidAdapter_spec.js | 4 +- 2 files changed, 13 insertions(+), 36 deletions(-) diff --git a/modules/vidazooBidAdapter.js b/modules/vidazooBidAdapter.js index 382833fbca9..0718f22d0d2 100644 --- a/modules/vidazooBidAdapter.js +++ b/modules/vidazooBidAdapter.js @@ -12,14 +12,6 @@ const TTL_SECONDS = 60 * 5; const DEAL_ID_EXPIRY = 1000 * 60 * 15; const UNIQUE_DEAL_ID_EXPIRY = 1000 * 60 * 15; const SESSION_ID_KEY = 'vidSid'; -const INTERNAL_SYNC_TYPE = { - IFRAME: 'iframe', - IMAGE: 'img' -}; -const EXTERNAL_SYNC_TYPE = { - IFRAME: 'iframe', - IMAGE: 'image' -}; export const SUPPORTED_ID_SYSTEMS = { 'britepoolid': 1, 'criteoId': 1, @@ -176,39 +168,24 @@ function interpretResponse(serverResponse, request) { } } -function getUserSyncs(syncOptions, responses) { +function getUserSyncs(syncOptions, responses, gdprConsent = {}, uspConsent = '') { + let syncs = []; const { iframeEnabled, pixelEnabled } = syncOptions; - + const { gdprApplies, consentString = '' } = gdprConsent; + const params = `?gdpr=${gdprApplies ? 1 : 0}&gdpr_consent=${encodeURIComponent(consentString || '')}&us_privacy=${encodeURIComponent(uspConsent || '')}` if (iframeEnabled) { - return [{ + syncs.push({ type: 'iframe', - url: 'https://static.cootlogix.com/basev/sync/user_sync.html' - }]; + url: `https://prebid.cootlogix.com/api/sync/iframe/${params}` + }); } - if (pixelEnabled) { - const lookup = {}; - const syncs = []; - responses.forEach(response => { - const { body } = response; - const results = body ? body.results || [] : []; - results.forEach(result => { - (result.cookies || []).forEach(cookie => { - if (cookie.type === INTERNAL_SYNC_TYPE.IMAGE) { - if (pixelEnabled && !lookup[cookie.src]) { - syncs.push({ - type: EXTERNAL_SYNC_TYPE.IMAGE, - url: cookie.src - }); - } - } - }); - }); + syncs.push({ + type: 'image', + url: `https://prebid.cootlogix.com/api/sync/image/${params}` }); - return syncs; } - - return []; + return syncs; } export function hashCode(s, prefix = '_') { diff --git a/test/spec/modules/vidazooBidAdapter_spec.js b/test/spec/modules/vidazooBidAdapter_spec.js index 8b3a492d2e5..1a503e46b5c 100644 --- a/test/spec/modules/vidazooBidAdapter_spec.js +++ b/test/spec/modules/vidazooBidAdapter_spec.js @@ -179,7 +179,7 @@ describe('VidazooBidAdapter', function () { expect(result).to.deep.equal([{ type: 'iframe', - url: 'https://static.cootlogix.com/basev/sync/user_sync.html' + url: 'https://prebid.cootlogix.com/api/sync/iframe/?gdpr=0&gdpr_consent=&us_privacy=' }]); }); @@ -187,7 +187,7 @@ describe('VidazooBidAdapter', function () { const result = adapter.getUserSyncs({ pixelEnabled: true }, [SERVER_RESPONSE]); expect(result).to.deep.equal([{ - 'url': 'https://sync.com', + 'url': 'https://prebid.cootlogix.com/api/sync/image/?gdpr=0&gdpr_consent=&us_privacy=', 'type': 'image' }]); }) From 783a3bb9af8788584e2ee27f7853a13d73f6ecf9 Mon Sep 17 00:00:00 2001 From: Junus Date: Mon, 7 Sep 2020 22:49:51 +0600 Subject: [PATCH 07/34] New Bid Adapter: a4g (#5688) * Updated a4g adapter * use https and mediaTypes sizes --- modules/a4gBidAdapter.js | 90 ++++++++++++ modules/a4gBidAdapter.md | 18 ++- test/spec/modules/a4gBidAdapter_spec.js | 173 ++++++++++++++++++++++++ 3 files changed, 276 insertions(+), 5 deletions(-) create mode 100644 modules/a4gBidAdapter.js create mode 100644 test/spec/modules/a4gBidAdapter_spec.js diff --git a/modules/a4gBidAdapter.js b/modules/a4gBidAdapter.js new file mode 100644 index 00000000000..b7d8722e9f9 --- /dev/null +++ b/modules/a4gBidAdapter.js @@ -0,0 +1,90 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import * as utils from '../src/utils.js'; + +const A4G_BIDDER_CODE = 'a4g'; +const A4G_CURRENCY = 'USD'; +const A4G_DEFAULT_BID_URL = 'https://ads.ad4game.com/v1/bid'; +const A4G_TTL = 120; + +const LOCATION_PARAM_NAME = 'siteurl'; +const ID_PARAM_NAME = 'id'; +const IFRAME_PARAM_NAME = 'if'; +const ZONE_ID_PARAM_NAME = 'zoneId'; +const SIZE_PARAM_NAME = 'size'; + +const ARRAY_PARAM_SEPARATOR = ';'; +const ARRAY_SIZE_SEPARATOR = ','; +const SIZE_SEPARATOR = 'x'; + +export const spec = { + code: A4G_BIDDER_CODE, + isBidRequestValid: function(bid) { + return bid.params && !!bid.params.zoneId; + }, + + buildRequests: function(validBidRequests, bidderRequest) { + let deliveryUrl = ''; + const idParams = []; + const sizeParams = []; + const zoneIds = []; + + utils._each(validBidRequests, function(bid) { + if (!deliveryUrl && typeof bid.params.deliveryUrl === 'string') { + deliveryUrl = bid.params.deliveryUrl; + } + idParams.push(bid.bidId); + let bidSizes = (bid.mediaTypes && bid.mediaTypes.banner && bid.mediaTypes.banner.sizes) || bid.sizes; + sizeParams.push(bidSizes.map(size => size.join(SIZE_SEPARATOR)).join(ARRAY_SIZE_SEPARATOR)); + zoneIds.push(bid.params.zoneId); + }); + + if (!deliveryUrl) { + deliveryUrl = A4G_DEFAULT_BID_URL; + } + + let data = { + [IFRAME_PARAM_NAME]: 0, + [LOCATION_PARAM_NAME]: (bidderRequest.refererInfo && bidderRequest.refererInfo.referer) ? bidderRequest.refererInfo.referer : window.location.href, + [SIZE_PARAM_NAME]: sizeParams.join(ARRAY_PARAM_SEPARATOR), + [ID_PARAM_NAME]: idParams.join(ARRAY_PARAM_SEPARATOR), + [ZONE_ID_PARAM_NAME]: zoneIds.join(ARRAY_PARAM_SEPARATOR) + }; + + if (bidderRequest && bidderRequest.gdprConsent) { + data.gdpr = { + applies: bidderRequest.gdprConsent.gdprApplies, + consent: bidderRequest.gdprConsent.consentString + }; + } + + return { + method: 'GET', + url: deliveryUrl, + data: data + }; + }, + + interpretResponse: function(serverResponses, request) { + const bidResponses = []; + utils._each(serverResponses.body, function(response) { + if (response.cpm > 0) { + const bidResponse = { + requestId: response.id, + creativeId: response.id, + adId: response.id, + cpm: response.cpm, + width: response.width, + height: response.height, + currency: A4G_CURRENCY, + netRevenue: true, + ttl: A4G_TTL, + ad: response.ad + }; + bidResponses.push(bidResponse); + } + }); + return bidResponses; + } +}; + +registerBidder(spec); diff --git a/modules/a4gBidAdapter.md b/modules/a4gBidAdapter.md index dcab312ed29..70f110724b0 100644 --- a/modules/a4gBidAdapter.md +++ b/modules/a4gBidAdapter.md @@ -6,32 +6,40 @@ Maintainer: devops@ad4game.com # Description -Ad4Game Bidder Adapter for Prebid.js. It should be tested on real domain. `localhost` should be rewritten (ex. example.com). +Ad4Game Bidder Adapter for Prebid.js. It should be tested on real domain. `localhost` should be rewritten (ex. example.com). # Test Parameters ``` var adUnits = [ { code: 'test-div', - sizes: [[300, 250]], // a display size + mediaTypes: { + banner: { + sizes: [[300, 250]], // a display size + } + }, bids: [ { bidder: 'a4g', params: { zoneId: 59304, - deliveryUrl: 'http://dev01.ad4game.com/v1/bid' + deliveryUrl: 'https://dev01.ad4game.com/v1/bid' } } ] },{ code: 'test-div', - sizes: [[300, 50]], // a mobile size + mediaTypes: { + banner: { + sizes: [[300, 50]], // a mobile size + } + }, bids: [ { bidder: 'a4g', params: { zoneId: 59354, - deliveryUrl: 'http://dev01.ad4game.com/v1/bid' + deliveryUrl: 'https://dev01.ad4game.com/v1/bid' } } ] diff --git a/test/spec/modules/a4gBidAdapter_spec.js b/test/spec/modules/a4gBidAdapter_spec.js new file mode 100644 index 00000000000..3dccbb28426 --- /dev/null +++ b/test/spec/modules/a4gBidAdapter_spec.js @@ -0,0 +1,173 @@ +import { expect } from 'chai'; +import { spec } from 'modules/a4gBidAdapter.js'; + +describe('a4gAdapterTests', function () { + describe('bidRequestValidity', function () { + it('bidRequest with zoneId and deliveryUrl params', function () { + expect(spec.isBidRequestValid({ + bidder: 'a4g', + params: { + zoneId: 59304, + deliveryUrl: 'http://dev01.ad4game.com/v1/bid' + } + })).to.equal(true); + }); + + it('bidRequest with only zoneId', function () { + expect(spec.isBidRequestValid({ + bidder: 'a4g', + params: { + zoneId: 59304 + } + })).to.equal(true); + }); + + it('bidRequest with only deliveryUrl', function () { + expect(spec.isBidRequestValid({ + bidder: 'a4g', + params: { + deliveryUrl: 'http://dev01.ad4game.com/v1/bid' + } + })).to.equal(false); + }); + }); + + describe('bidRequest', function () { + const DEFAULT_OPTION = { + refererInfo: { + referer: 'https://www.prebid.org', + canonicalUrl: 'https://www.prebid.org/the/link/to/the/page' + } + }; + + const bidRequests = [{ + 'bidder': 'a4g', + 'bidId': '51ef8751f9aead', + 'params': { + 'zoneId': 59304, + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 50], [300, 250], [300, 600]] + } + }, + 'sizes': [[320, 50], [300, 250], [300, 600]], + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757' + }, { + 'bidder': 'a4g', + 'bidId': '51ef8751f9aead', + 'params': { + 'zoneId': 59354, + 'deliveryUrl': '//dev01.ad4game.com/v1/bid' + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 50], [300, 250], [300, 600]] + } + }, + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757' + }]; + + it('bidRequest method', function () { + const request = spec.buildRequests(bidRequests, DEFAULT_OPTION); + expect(request.method).to.equal('GET'); + }); + + it('bidRequest url', function () { + const request = spec.buildRequests(bidRequests, DEFAULT_OPTION); + expect(request.url).to.match(new RegExp(`${bidRequests[1].params.deliveryUrl}`)); + }); + + it('bidRequest data', function () { + const request = spec.buildRequests(bidRequests, DEFAULT_OPTION); + expect(request.data).to.exist; + }); + + it('bidRequest zoneIds', function () { + const request = spec.buildRequests(bidRequests, DEFAULT_OPTION); + expect(request.data.zoneId).to.equal('59304;59354'); + }); + + it('bidRequest gdpr consent', function () { + const consentString = 'consentString'; + const bidderRequest = { + bidderCode: 'a4g', + auctionId: '18fd8b8b0bd757', + bidderRequestId: '418b37f85e772c', + timeout: 3000, + gdprConsent: { + consentString: consentString, + gdprApplies: true + }, + refererInfo: { + referer: 'https://www.prebid.org', + canonicalUrl: 'https://www.prebid.org/the/link/to/the/page' + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + + expect(request.data.gdpr).to.exist; + expect(request.data.gdpr.applies).to.exist.and.to.be.true; + expect(request.data.gdpr.consent).to.exist.and.to.equal(consentString); + }); + }); + + describe('interpretResponse', function () { + const bidRequest = [{ + 'bidder': 'a4g', + 'bidId': '51ef8751f9aead', + 'params': { + 'zoneId': 59304, + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 50], [300, 250], [300, 600]] + } + }, + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757' + }]; + + const bidResponse = { + body: [{ + 'id': 'div-gpt-ad-1460505748561-0', + 'ad': 'test ad', + 'width': 320, + 'height': 250, + 'cpm': 5.2 + }], + headers: {} + }; + + it('required keys', function () { + const result = spec.interpretResponse(bidResponse, bidRequest); + + let requiredKeys = [ + 'requestId', + 'creativeId', + 'adId', + 'cpm', + 'width', + 'height', + 'currency', + 'netRevenue', + 'ttl', + 'ad' + ]; + + let resultKeys = Object.keys(result[0]); + resultKeys.forEach(function(key) { + expect(requiredKeys.indexOf(key) !== -1).to.equal(true); + }); + }) + }); +}); From cacb5eb7412b5f61886f52cd409fee1809a5400e Mon Sep 17 00:00:00 2001 From: ofirpaBrowsi <55348874+ofirpaBrowsi@users.noreply.github.com> Date: Tue, 8 Sep 2020 18:39:37 +0300 Subject: [PATCH 08/34] Adding errors event listener (#5563) * Adding errors event listener * Changed event name to 'auctionDebug', with 'type' property, to support future debug event types. * Update AnalyticsAdapter.js * Update AnalyticsAdapter_spec.js * Update AnalyticsAdapter_spec.js * Update AnalyticsAdapter.js * Removed trailing spaces * fixed tests assertion to handle new error events * Fixed analytics test to expect auctionDebug event too * Update yuktamediaAnalyticsAdapter_spec.js * Removed port dependency on readpeak adapter's test Co-authored-by: Patrick McCann --- src/AnalyticsAdapter.js | 2 + src/constants.json | 3 +- src/utils.js | 2 + test/spec/AnalyticsAdapter_spec.js | 12 ++ .../prebidmanagerAnalyticsAdapter_spec.js | 2 +- test/spec/modules/readpeakBidAdapter_spec.js | 13 +- .../modules/sigmoidAnalyticsAdapter_spec.js | 2 +- .../yuktamediaAnalyticsAdapter_spec.js | 176 +++++++++++++++++- 8 files changed, 196 insertions(+), 16 deletions(-) diff --git a/src/AnalyticsAdapter.js b/src/AnalyticsAdapter.js index f3297412a35..80c12a3eb8e 100644 --- a/src/AnalyticsAdapter.js +++ b/src/AnalyticsAdapter.js @@ -18,6 +18,7 @@ const { BIDDER_DONE, SET_TARGETING, AD_RENDER_FAILED, + AUCTION_DEBUG, ADD_AD_UNITS } } = CONSTANTS; @@ -112,6 +113,7 @@ export default function AnalyticsAdapter({ url, analyticsType, global, handler } [SET_TARGETING]: args => this.enqueue({ eventType: SET_TARGETING, args }), [AUCTION_END]: args => this.enqueue({ eventType: AUCTION_END, args }), [AD_RENDER_FAILED]: args => this.enqueue({ eventType: AD_RENDER_FAILED, args }), + [AUCTION_DEBUG]: args => this.enqueue({ eventType: AUCTION_DEBUG, args }), [ADD_AD_UNITS]: args => this.enqueue({ eventType: ADD_AD_UNITS, args }), [AUCTION_INIT]: args => { args.config = typeof config === 'object' ? config.options || {} : {}; // enableAnaltyics configuration object diff --git a/src/constants.json b/src/constants.json index 1b5feda6a05..7c0af445cdb 100644 --- a/src/constants.json +++ b/src/constants.json @@ -37,7 +37,8 @@ "REQUEST_BIDS": "requestBids", "ADD_AD_UNITS": "addAdUnits", "AD_RENDER_FAILED": "adRenderFailed", - "TCF2_ENFORCEMENT": "tcf2Enforcement" + "TCF2_ENFORCEMENT": "tcf2Enforcement", + "AUCTION_DEBUG": "auctionDebug" }, "AD_RENDER_FAILED_REASON" : { "PREVENT_WRITING_ON_MAIN_DOCUMENT": "preventWritingOnMainDocuemnt", diff --git a/src/utils.js b/src/utils.js index 591c1d1bb2b..9426308daf4 100644 --- a/src/utils.js +++ b/src/utils.js @@ -21,6 +21,7 @@ let consoleLogExists = Boolean(consoleExists && window.console.log); let consoleInfoExists = Boolean(consoleExists && window.console.info); let consoleWarnExists = Boolean(consoleExists && window.console.warn); let consoleErrorExists = Boolean(consoleExists && window.console.error); +var events = require('./events.js'); // this allows stubbing of utility functions that are used internally by other utility functions export const internal = { @@ -261,6 +262,7 @@ export function logError() { if (debugTurnedOn() && consoleErrorExists) { console.error.apply(console, decorateLog(arguments, 'ERROR:')); } + events.emit(CONSTANTS.EVENTS.AUCTION_DEBUG, {type: 'ERROR', arguments: arguments}); } function decorateLog(args, prefix) { diff --git a/test/spec/AnalyticsAdapter_spec.js b/test/spec/AnalyticsAdapter_spec.js index 4afa430f81e..71fb9f87fa0 100644 --- a/test/spec/AnalyticsAdapter_spec.js +++ b/test/spec/AnalyticsAdapter_spec.js @@ -9,6 +9,7 @@ const BID_RESPONSE = CONSTANTS.EVENTS.BID_RESPONSE; const BID_WON = CONSTANTS.EVENTS.BID_WON; const BID_TIMEOUT = CONSTANTS.EVENTS.BID_TIMEOUT; const AD_RENDER_FAILED = CONSTANTS.EVENTS.AD_RENDER_FAILED; +const AUCTION_DEBUG = CONSTANTS.EVENTS.AUCTION_DEBUG; const ADD_AD_UNITS = CONSTANTS.EVENTS.ADD_AD_UNITS; const AnalyticsAdapter = require('src/AnalyticsAdapter').default; @@ -83,6 +84,17 @@ FEATURE: Analytics Adapters API expect(result).to.deep.equal({args: {call: 'adRenderFailed'}, eventType: 'adRenderFailed'}); }); + it('SHOULD call global when an auction debug event occurs', function () { + const eventType = AUCTION_DEBUG; + const args = { call: 'auctionDebug' }; + + adapter.enableAnalytics(); + events.emit(eventType, args); + + let result = JSON.parse(server.requests[0].requestBody); + expect(result).to.deep.equal({args: {call: 'auctionDebug'}, eventType: 'auctionDebug'}); + }); + it('SHOULD call global when an addAdUnits event occurs', function () { const eventType = ADD_AD_UNITS; const args = { call: 'addAdUnits' }; diff --git a/test/spec/modules/prebidmanagerAnalyticsAdapter_spec.js b/test/spec/modules/prebidmanagerAnalyticsAdapter_spec.js index e87be40314c..ce97789fe3e 100644 --- a/test/spec/modules/prebidmanagerAnalyticsAdapter_spec.js +++ b/test/spec/modules/prebidmanagerAnalyticsAdapter_spec.js @@ -98,7 +98,7 @@ describe('Prebid Manager Analytics Adapter', function () { events.emit(constants.EVENTS.AUCTION_END, {}); events.emit(constants.EVENTS.BID_TIMEOUT, {}); - sinon.assert.callCount(prebidmanagerAnalytics.track, 6); + sinon.assert.callCount(prebidmanagerAnalytics.track, 7); }); }); diff --git a/test/spec/modules/readpeakBidAdapter_spec.js b/test/spec/modules/readpeakBidAdapter_spec.js index eb9077fac39..0c6f942e724 100644 --- a/test/spec/modules/readpeakBidAdapter_spec.js +++ b/test/spec/modules/readpeakBidAdapter_spec.js @@ -177,15 +177,10 @@ describe('ReadPeakAdapter', function() { expect(data.id).to.equal(bidRequest.bidderRequestId); expect(data.imp[0].bidfloor).to.equal(bidRequest.params.bidfloor); expect(data.imp[0].bidfloorcur).to.equal('USD'); - expect(data.site).to.deep.equal({ - publisher: { - id: bidRequest.params.publisherId, - domain: 'http://localhost:9876' - }, - id: bidRequest.params.siteId, - page: bidderRequest.refererInfo.referer, - domain: parseUrl(bidderRequest.refererInfo.referer).hostname - }); + expect(data.site.publisher.id).to.equal(bidRequest.params.publisherId); + expect(data.site.id).to.equal(bidRequest.params.siteId); + expect(data.site.page).to.equal(bidderRequest.refererInfo.referer); + expect(data.site.domain).to.equal(parseUrl(bidderRequest.refererInfo.referer).hostname); expect(data.device).to.deep.contain({ ua: navigator.userAgent, language: navigator.language diff --git a/test/spec/modules/sigmoidAnalyticsAdapter_spec.js b/test/spec/modules/sigmoidAnalyticsAdapter_spec.js index 75afb0ed86e..854c3a8e22d 100644 --- a/test/spec/modules/sigmoidAnalyticsAdapter_spec.js +++ b/test/spec/modules/sigmoidAnalyticsAdapter_spec.js @@ -38,7 +38,7 @@ describe('sigmoid Prebid Analytic', function () { events.emit(constants.EVENTS.BID_RESPONSE, {}); events.emit(constants.EVENTS.BID_WON, {}); - sinon.assert.callCount(sigmoidAnalytic.track, 5); + sinon.assert.callCount(sigmoidAnalytic.track, 7); }); }); describe('build utm tag data', function () { diff --git a/test/spec/modules/yuktamediaAnalyticsAdapter_spec.js b/test/spec/modules/yuktamediaAnalyticsAdapter_spec.js index 24781d749e0..c8643c547e0 100644 --- a/test/spec/modules/yuktamediaAnalyticsAdapter_spec.js +++ b/test/spec/modules/yuktamediaAnalyticsAdapter_spec.js @@ -391,7 +391,7 @@ describe('yuktamedia analytics adapter', function () { yuktamediaAnalyticsAdapter.track.restore(); }); - it('should catch all events', function () { + it('should catch all events 1', function () { yuktamediaAnalyticsAdapter.enableAnalytics({ provider: 'yuktamedia', options: { @@ -403,12 +403,82 @@ describe('yuktamedia analytics adapter', function () { } }); events.emit(constants.EVENTS.AUCTION_INIT, prebidAuction[constants.EVENTS.AUCTION_INIT]); + sinon.assert.called(yuktamediaAnalyticsAdapter.track); + }); + + it('should catch all events 2', function () { + yuktamediaAnalyticsAdapter.enableAnalytics({ + provider: 'yuktamedia', + options: { + pubId: '1', + pubKey: 'ZXlKaGJHY2lPaUpJVXpJMU5pSjkuT==', + enableUTMCollection: true, + enableSession: true, + enableUserIdCollection: true + } + }); events.emit(constants.EVENTS.BID_REQUESTED, prebidAuction[constants.EVENTS.BID_REQUESTED]); + sinon.assert.called(yuktamediaAnalyticsAdapter.track); + }); + + it('should catch all events 3', function () { + yuktamediaAnalyticsAdapter.enableAnalytics({ + provider: 'yuktamedia', + options: { + pubId: '1', + pubKey: 'ZXlKaGJHY2lPaUpJVXpJMU5pSjkuT==', + enableUTMCollection: true, + enableSession: true, + enableUserIdCollection: true + } + }); events.emit(constants.EVENTS.NO_BID, prebidAuction[constants.EVENTS.NO_BID]); + sinon.assert.called(yuktamediaAnalyticsAdapter.track); + }); + + it('should catch all events 4', function () { + yuktamediaAnalyticsAdapter.enableAnalytics({ + provider: 'yuktamedia', + options: { + pubId: '1', + pubKey: 'ZXlKaGJHY2lPaUpJVXpJMU5pSjkuT==', + enableUTMCollection: true, + enableSession: true, + enableUserIdCollection: true + } + }); events.emit(constants.EVENTS.BID_TIMEOUT, prebidAuction[constants.EVENTS.BID_TIMEOUT]); + sinon.assert.called(yuktamediaAnalyticsAdapter.track); + }); + + it('should catch all events 5', function () { + yuktamediaAnalyticsAdapter.enableAnalytics({ + provider: 'yuktamedia', + options: { + pubId: '1', + pubKey: 'ZXlKaGJHY2lPaUpJVXpJMU5pSjkuT==', + enableUTMCollection: true, + enableSession: true, + enableUserIdCollection: true + } + }); events.emit(constants.EVENTS.BID_RESPONSE, prebidAuction[constants.EVENTS.BID_RESPONSE]); + sinon.assert.called(yuktamediaAnalyticsAdapter.track); + }); + + it('should catch all events 6', function () { + yuktamediaAnalyticsAdapter.enableAnalytics({ + provider: 'yuktamedia', + options: { + pubId: '1', + pubKey: 'ZXlKaGJHY2lPaUpJVXpJMU5pSjkuT==', + enableUTMCollection: true, + enableSession: true, + enableUserIdCollection: true + } + }); events.emit(constants.EVENTS.AUCTION_END, prebidAuction[constants.EVENTS.AUCTION_END]); - sinon.assert.callCount(yuktamediaAnalyticsAdapter.track, 6); + sinon.assert.called(yuktamediaAnalyticsAdapter.track); }); it('should catch no events if no pubKey and pubId', function () { @@ -427,7 +497,7 @@ describe('yuktamedia analytics adapter', function () { sinon.assert.callCount(yuktamediaAnalyticsAdapter.track, 0); }); - it('should catch nobid, timeout and biwon event events', function () { + it('should catch nobid, timeout and bidwon event events one of eight', function () { yuktamediaAnalyticsAdapter.enableAnalytics({ provider: 'yuktamedia', options: { @@ -439,14 +509,112 @@ describe('yuktamedia analytics adapter', function () { } }); events.emit(constants.EVENTS.AUCTION_INIT, prebidNativeAuction[constants.EVENTS.AUCTION_INIT]); + sinon.assert.called(yuktamediaAnalyticsAdapter.track); + }); + + it('should catch nobid, timeout and bidwon event events two of eight', function () { + yuktamediaAnalyticsAdapter.enableAnalytics({ + provider: 'yuktamedia', + options: { + pubId: '1', + pubKey: 'ZXlKaGJHY2lPaUpJVXpJMU5pSjkuT==', + enableUTMCollection: true, + enableSession: true, + enableUserIdCollection: true + } + }); events.emit(constants.EVENTS.BID_REQUESTED, prebidNativeAuction[constants.EVENTS.BID_REQUESTED]); + sinon.assert.called(yuktamediaAnalyticsAdapter.track); + }); + + it('should catch nobid, timeout and bidwon event events three of eight', function () { + yuktamediaAnalyticsAdapter.enableAnalytics({ + provider: 'yuktamedia', + options: { + pubId: '1', + pubKey: 'ZXlKaGJHY2lPaUpJVXpJMU5pSjkuT==', + enableUTMCollection: true, + enableSession: true, + enableUserIdCollection: true + } + }); events.emit(constants.EVENTS.BID_REQUESTED, prebidNativeAuction[constants.EVENTS.BID_REQUESTED + '1']); + sinon.assert.called(yuktamediaAnalyticsAdapter.track); + }); + + it('should catch nobid, timeout and bidwon event events four of eight', function () { + yuktamediaAnalyticsAdapter.enableAnalytics({ + provider: 'yuktamedia', + options: { + pubId: '1', + pubKey: 'ZXlKaGJHY2lPaUpJVXpJMU5pSjkuT==', + enableUTMCollection: true, + enableSession: true, + enableUserIdCollection: true + } + }); events.emit(constants.EVENTS.NO_BID, prebidNativeAuction[constants.EVENTS.NO_BID]); + sinon.assert.called(yuktamediaAnalyticsAdapter.track); + }); + + it('should catch nobid, timeout and bidwon event events five of eight', function () { + yuktamediaAnalyticsAdapter.enableAnalytics({ + provider: 'yuktamedia', + options: { + pubId: '1', + pubKey: 'ZXlKaGJHY2lPaUpJVXpJMU5pSjkuT==', + enableUTMCollection: true, + enableSession: true, + enableUserIdCollection: true + } + }); events.emit(constants.EVENTS.BID_TIMEOUT, prebidNativeAuction[constants.EVENTS.BID_TIMEOUT]); + sinon.assert.called(yuktamediaAnalyticsAdapter.track); + }); + + it('should catch nobid, timeout and bidwon event events six of eight', function () { + yuktamediaAnalyticsAdapter.enableAnalytics({ + provider: 'yuktamedia', + options: { + pubId: '1', + pubKey: 'ZXlKaGJHY2lPaUpJVXpJMU5pSjkuT==', + enableUTMCollection: true, + enableSession: true, + enableUserIdCollection: true + } + }); events.emit(constants.EVENTS.BID_RESPONSE, prebidNativeAuction[constants.EVENTS.BID_RESPONSE]); + sinon.assert.called(yuktamediaAnalyticsAdapter.track); + }); + + it('should catch nobid, timeout and bidwon event events seven of eight', function () { + yuktamediaAnalyticsAdapter.enableAnalytics({ + provider: 'yuktamedia', + options: { + pubId: '1', + pubKey: 'ZXlKaGJHY2lPaUpJVXpJMU5pSjkuT==', + enableUTMCollection: true, + enableSession: true, + enableUserIdCollection: true + } + }); events.emit(constants.EVENTS.AUCTION_END, prebidNativeAuction[constants.EVENTS.AUCTION_END]); + sinon.assert.called(yuktamediaAnalyticsAdapter.track); + }); + + it('should catch nobid, timeout and bidwon event events eight of eight', function () { + yuktamediaAnalyticsAdapter.enableAnalytics({ + provider: 'yuktamedia', + options: { + pubId: '1', + pubKey: 'ZXlKaGJHY2lPaUpJVXpJMU5pSjkuT==', + enableUTMCollection: true, + enableSession: true, + enableUserIdCollection: true + } + }); events.emit(constants.EVENTS.AUCTION_END, prebidNativeAuction[constants.EVENTS.BID_WON]); - sinon.assert.callCount(yuktamediaAnalyticsAdapter.track, 8); + sinon.assert.called(yuktamediaAnalyticsAdapter.track); }); }); From 33e1691498e979a737e975fdd52191e455a7774f Mon Sep 17 00:00:00 2001 From: Scott Menzer Date: Tue, 8 Sep 2020 17:43:09 +0200 Subject: [PATCH 09/34] Prebid 4.7.0 Release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3fdc3ba4b83..846a35e4127 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.7.0-pre", + "version": "4.7.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From bcf7b5ada39ea1716ec7b25fe9060aefa1ffaf45 Mon Sep 17 00:00:00 2001 From: Drilon Kastrati Date: Tue, 8 Sep 2020 18:13:33 +0200 Subject: [PATCH 10/34] added adapters for gjirafa and malltv (#5587) * added adapters for gjirafa and malltv * interpretResponse fix for empty result * updated testing propertyId and placementId --- modules/gjirafaBidAdapter.js | 91 +++++++++++++ modules/gjirafaBidAdapter.md | 69 ++++++---- modules/malltvBidAdapter.js | 91 +++++++++++++ modules/malltvBidAdapter.md | 51 +++++++ test/spec/modules/gjirafaBidAdapter_spec.js | 140 ++++++++++++++++++++ test/spec/modules/malltvBidAdapter_spec.js | 140 ++++++++++++++++++++ 6 files changed, 555 insertions(+), 27 deletions(-) create mode 100644 modules/gjirafaBidAdapter.js create mode 100644 modules/malltvBidAdapter.js create mode 100644 modules/malltvBidAdapter.md create mode 100644 test/spec/modules/gjirafaBidAdapter_spec.js create mode 100644 test/spec/modules/malltvBidAdapter_spec.js diff --git a/modules/gjirafaBidAdapter.js b/modules/gjirafaBidAdapter.js new file mode 100644 index 00000000000..ca7fb4af32d --- /dev/null +++ b/modules/gjirafaBidAdapter.js @@ -0,0 +1,91 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; + +const BIDDER_CODE = 'gjirafa'; +const ENDPOINT_URL = 'https://central.gjirafa.com/bid'; +const DIMENSION_SEPARATOR = 'x'; +const SIZE_SEPARATOR = ';'; + +export const spec = { + code: BIDDER_CODE, + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function (bid) { + return !!(bid.params.propertyId && bid.params.placementId); + }, + /** + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function (validBidRequests, bidderRequest) { + let response = validBidRequests.map(bidRequest => { + let sizes = generateSizeParam(bidRequest.sizes); + let propertyId = bidRequest.params.propertyId; + let placementId = bidRequest.params.placementId; + let adUnitId = bidRequest.adUnitCode; + let pageViewGuid = bidRequest.params.pageViewGuid || ''; + let contents = bidRequest.params.contents || []; + const body = { + sizes: sizes, + adUnitId: adUnitId, + placementId: placementId, + propertyId: propertyId, + pageViewGuid: pageViewGuid, + url: bidderRequest ? bidderRequest.refererInfo.referer : '', + requestid: bidRequest.bidderRequestId, + bidid: bidRequest.bidId, + contents: contents + }; + return { + method: 'POST', + url: ENDPOINT_URL, + data: body + }; + }); + return response + }, + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse, bidRequest) { + window.adnResponse = serverResponse; + const responses = serverResponse.body; + const bidResponses = []; + for (var i = 0; i < responses.length; i++) { + const bidResponse = { + requestId: bidRequest.data.bidid, + cpm: responses[i].CPM, + width: responses[i].Width, + height: responses[i].Height, + creativeId: responses[i].CreativeId, + currency: responses[i].Currency, + netRevenue: responses[i].NetRevenue, + ttl: responses[i].TTL, + referrer: responses[i].Referrer, + ad: responses[i].Ad + }; + bidResponses.push(bidResponse); + } + return bidResponses; + } +} + +/** +* Generate size param for bid request using sizes array +* +* @param {Array} sizes Possible sizes for the ad unit. +* @return {string} Processed sizes param to be used for the bid request. +*/ +function generateSizeParam(sizes) { + return sizes.map(size => size.join(DIMENSION_SEPARATOR)).join(SIZE_SEPARATOR); +} + +registerBidder(spec); diff --git a/modules/gjirafaBidAdapter.md b/modules/gjirafaBidAdapter.md index 1ec8222d8de..53d3a76c5ed 100644 --- a/modules/gjirafaBidAdapter.md +++ b/modules/gjirafaBidAdapter.md @@ -1,36 +1,51 @@ # Overview Module Name: Gjirafa Bidder Adapter Module Type: Bidder Adapter -Maintainer: agonq@gjirafa.com +Maintainer: drilon@gjirafa.com # Description Gjirafa Bidder Adapter for Prebid.js. # Test Parameters var adUnits = [ -{ - code: 'test-div', - sizes: [[728, 90]], // leaderboard - bids: [ - { - bidder: 'gjirafa', - params: { - placementId: '71-3' - } - } - ] -},{ - code: 'test-div', - sizes: [[300, 250]], // mobile rectangle - bids: [ - { - bidder: 'gjirafa', - params: { - minCPM: 0.0001, - minCPC: 0.001, - explicit: true - } - } - ] -} -]; \ No newline at end of file + { + code: 'test-div', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + bids: [ + { + bidder: 'gjirafa', + params: { + propertyId: '105227', + placementId: '846841' + } + } + ] + }, + { + code: 'test-div', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [ + { + bidder: 'gjirafa', + params: { + propertyId: '105227', + placementId: '846848', + contents: [ //optional + { + type: 'article', + id: '123' + } + ] + } + } + ] + } +]; diff --git a/modules/malltvBidAdapter.js b/modules/malltvBidAdapter.js new file mode 100644 index 00000000000..4cdb5d45328 --- /dev/null +++ b/modules/malltvBidAdapter.js @@ -0,0 +1,91 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; + +const BIDDER_CODE = 'malltv'; +const ENDPOINT_URL = 'https://central.mall.tv/bid'; +const DIMENSION_SEPARATOR = 'x'; +const SIZE_SEPARATOR = ';'; + +export const spec = { + code: BIDDER_CODE, + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function (bid) { + return !!(bid.params.propertyId && bid.params.placementId); + }, + /** + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function (validBidRequests, bidderRequest) { + let response = validBidRequests.map(bidRequest => { + let sizes = generateSizeParam(bidRequest.sizes); + let propertyId = bidRequest.params.propertyId; + let placementId = bidRequest.params.placementId; + let adUnitId = bidRequest.adUnitCode; + let pageViewGuid = bidRequest.params.pageViewGuid || ''; + let contents = bidRequest.params.contents || []; + const body = { + sizes: sizes, + adUnitId: adUnitId, + placementId: placementId, + propertyId: propertyId, + pageViewGuid: pageViewGuid, + url: bidderRequest ? bidderRequest.refererInfo.referer : '', + requestid: bidRequest.bidderRequestId, + bidid: bidRequest.bidId, + contents: contents + }; + return { + method: 'POST', + url: ENDPOINT_URL, + data: body + }; + }); + return response + }, + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse, bidRequest) { + window.adnResponse = serverResponse; + const responses = serverResponse.body; + const bidResponses = []; + for (var i = 0; i < responses.length; i++) { + const bidResponse = { + requestId: bidRequest.data.bidid, + cpm: responses[i].CPM, + width: responses[i].Width, + height: responses[i].Height, + creativeId: responses[i].CreativeId, + currency: responses[i].Currency, + netRevenue: responses[i].NetRevenue, + ttl: responses[i].TTL, + referrer: responses[i].Referrer, + ad: responses[i].Ad + }; + bidResponses.push(bidResponse); + } + return bidResponses; + } +} + +/** +* Generate size param for bid request using sizes array +* +* @param {Array} sizes Possible sizes for the ad unit. +* @return {string} Processed sizes param to be used for the bid request. +*/ +function generateSizeParam(sizes) { + return sizes.map(size => size.join(DIMENSION_SEPARATOR)).join(SIZE_SEPARATOR); +} + +registerBidder(spec); diff --git a/modules/malltvBidAdapter.md b/modules/malltvBidAdapter.md new file mode 100644 index 00000000000..72db0cef6c7 --- /dev/null +++ b/modules/malltvBidAdapter.md @@ -0,0 +1,51 @@ +# Overview +Module Name: MallTV Bidder Adapter Module +Type: Bidder Adapter +Maintainer: drilon@gjirafa.com + +# Description +MallTV Bidder Adapter for Prebid.js. + +# Test Parameters +var adUnits = [ + { + code: 'test-div', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 300]] + } + }, + bids: [ + { + bidder: 'malltv', + params: { + propertyId: '105134', + placementId: '846832' + } + } + ] + }, + { + code: 'test-div', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 300]] + } + }, + bids: [ + { + bidder: 'malltv', + params: { + propertyId: '105134', + placementId: '846832', + contents: [ //optional + { + type: 'video', + id: '123' + } + ] + } + } + ] + } +]; diff --git a/test/spec/modules/gjirafaBidAdapter_spec.js b/test/spec/modules/gjirafaBidAdapter_spec.js new file mode 100644 index 00000000000..566b1243f62 --- /dev/null +++ b/test/spec/modules/gjirafaBidAdapter_spec.js @@ -0,0 +1,140 @@ +import { expect } from 'chai'; +import { spec } from 'modules/gjirafaBidAdapter'; + +describe('gjirafaAdapterTest', () => { + describe('bidRequestValidity', () => { + it('bidRequest with propertyId and placementId', () => { + expect(spec.isBidRequestValid({ + bidder: 'gjirafa', + params: { + propertyId: '{propertyId}', + placementId: '{placementId}' + } + })).to.equal(true); + }); + + it('bidRequest without propertyId', () => { + expect(spec.isBidRequestValid({ + bidder: 'gjirafa', + params: { + placementId: '{placementId}' + } + })).to.equal(false); + }); + + it('bidRequest without placementId', () => { + expect(spec.isBidRequestValid({ + bidder: 'gjirafa', + params: { + propertyId: '{propertyId}', + } + })).to.equal(false); + }); + + it('bidRequest without propertyId orplacementId', () => { + expect(spec.isBidRequestValid({ + bidder: 'gjirafa', + params: { + propertyId: '{propertyId}', + } + })).to.equal(false); + }); + }); + + describe('bidRequest', () => { + const bidRequests = [{ + 'bidder': 'gjirafa', + 'params': { + 'propertyId': '{propertyId}', + 'placementId': '{placementId}' + }, + 'adUnitCode': 'hb-leaderboard', + 'transactionId': 'b6b889bb-776c-48fd-bc7b-d11a1cf0425e', + 'sizes': [[728, 90]], + 'bidId': '10bdc36fe0b48c8', + 'bidderRequestId': '70deaff71c281d', + 'auctionId': 'f9012acc-b6b7-4748-9098-97252914f9dc' + }]; + + it('bidRequest HTTP method', () => { + const requests = spec.buildRequests(bidRequests); + requests.forEach(function (requestItem) { + expect(requestItem.method).to.equal('POST'); + }); + }); + + it('bidRequest url', () => { + const endpointUrl = 'https://central.gjirafa.com/bid'; + const requests = spec.buildRequests(bidRequests); + requests.forEach(function (requestItem) { + expect(requestItem.url).to.match(new RegExp(`${endpointUrl}`)); + }); + }); + + it('bidRequest data', () => { + const requests = spec.buildRequests(bidRequests); + requests.forEach(function (requestItem) { + expect(requestItem.data).to.exist; + }); + }); + + it('bidRequest sizes', () => { + const requests = spec.buildRequests(bidRequests); + expect(requests[0].data.sizes).to.equal('728x90'); + }); + }); + + describe('interpretResponse', () => { + const bidRequest = { + 'method': 'POST', + 'url': 'https://central.gjirafa.com/bid', + 'data': { + 'sizes': '728x90', + 'adUnitId': 'hb-leaderboard', + 'placementId': '{placementId}', + 'propertyId': '{propertyId}', + 'pageViewGuid': '{pageViewGuid}', + 'url': 'http://localhost:9999/integrationExamples/gpt/hello_world.html?pbjs_debug=true', + 'requestid': '26ee8fe87940da7', + 'bidid': '2962dbedc4768bf' + } + }; + + const bidResponse = { + body: [{ + 'CPM': 1, + 'Width': 728, + 'Height': 90, + 'Referrer': 'http://localhost:9999/integrationExamples/gpt/hello_world.html?pbjs_debug=true', + 'Ad': '
Test ad
', + 'CreativeId': '123abc', + 'NetRevenue': false, + 'Currency': 'EUR', + 'TTL': 360 + }], + headers: {} + }; + + it('all keys present', () => { + const result = spec.interpretResponse(bidResponse, bidRequest); + + let keys = [ + 'requestId', + 'cpm', + 'width', + 'height', + 'creativeId', + 'currency', + 'netRevenue', + 'ttl', + 'referrer', + 'ad' + ]; + + let resultKeys = Object.keys(result[0]); + resultKeys.forEach(function (key) { + expect(keys.indexOf(key) !== -1).to.equal(true); + }); + }) + }); +}); diff --git a/test/spec/modules/malltvBidAdapter_spec.js b/test/spec/modules/malltvBidAdapter_spec.js new file mode 100644 index 00000000000..10161b319c5 --- /dev/null +++ b/test/spec/modules/malltvBidAdapter_spec.js @@ -0,0 +1,140 @@ +import { expect } from 'chai'; +import { spec } from 'modules/malltvBidAdapter'; + +describe('malltvAdapterTest', () => { + describe('bidRequestValidity', () => { + it('bidRequest with propertyId and placementId', () => { + expect(spec.isBidRequestValid({ + bidder: 'malltv', + params: { + propertyId: '{propertyId}', + placementId: '{placementId}' + } + })).to.equal(true); + }); + + it('bidRequest without propertyId', () => { + expect(spec.isBidRequestValid({ + bidder: 'malltv', + params: { + placementId: '{placementId}' + } + })).to.equal(false); + }); + + it('bidRequest without placementId', () => { + expect(spec.isBidRequestValid({ + bidder: 'malltv', + params: { + propertyId: '{propertyId}', + } + })).to.equal(false); + }); + + it('bidRequest without propertyId or placementId', () => { + expect(spec.isBidRequestValid({ + bidder: 'malltv', + params: { + propertyId: '{propertyId}', + } + })).to.equal(false); + }); + }); + + describe('bidRequest', () => { + const bidRequests = [{ + 'bidder': 'malltv', + 'params': { + 'propertyId': '{propertyId}', + 'placementId': '{placementId}' + }, + 'adUnitCode': 'hb-leaderboard', + 'transactionId': 'b6b889bb-776c-48fd-bc7b-d11a1cf0425e', + 'sizes': [[300, 250]], + 'bidId': '10bdc36fe0b48c8', + 'bidderRequestId': '70deaff71c281d', + 'auctionId': 'f9012acc-b6b7-4748-9098-97252914f9dc' + }]; + + it('bidRequest HTTP method', () => { + const requests = spec.buildRequests(bidRequests); + requests.forEach(function (requestItem) { + expect(requestItem.method).to.equal('POST'); + }); + }); + + it('bidRequest url', () => { + const endpointUrl = 'https://central.mall.tv/bid'; + const requests = spec.buildRequests(bidRequests); + requests.forEach(function (requestItem) { + expect(requestItem.url).to.match(new RegExp(`${endpointUrl}`)); + }); + }); + + it('bidRequest data', () => { + const requests = spec.buildRequests(bidRequests); + requests.forEach(function (requestItem) { + expect(requestItem.data).to.exist; + }); + }); + + it('bidRequest sizes', () => { + const requests = spec.buildRequests(bidRequests); + expect(requests[0].data.sizes).to.equal('300x250'); + }); + }); + + describe('interpretResponse', () => { + const bidRequest = { + 'method': 'POST', + 'url': 'https://central.mall.tv/bid', + 'data': { + 'sizes': '300x250', + 'adUnitId': 'rectangle', + 'placementId': '{placementId}', + 'propertyId': '{propertyId}', + 'pageViewGuid': '{pageViewGuid}', + 'url': 'http://localhost:9999/integrationExamples/gpt/hello_world.html?pbjs_debug=true', + 'requestid': '26ee8fe87940da7', + 'bidid': '2962dbedc4768bf' + } + }; + + const bidResponse = { + body: [{ + 'CPM': 1, + 'Width': 300, + 'Height': 250, + 'Referrer': 'http://localhost:9999/integrationExamples/gpt/hello_world.html?pbjs_debug=true', + 'Ad': '
Test ad
', + 'CreativeId': '123abc', + 'NetRevenue': false, + 'Currency': 'EUR', + 'TTL': 360 + }], + headers: {} + }; + + it('all keys present', () => { + const result = spec.interpretResponse(bidResponse, bidRequest); + + let keys = [ + 'requestId', + 'cpm', + 'width', + 'height', + 'creativeId', + 'currency', + 'netRevenue', + 'ttl', + 'referrer', + 'ad' + ]; + + let resultKeys = Object.keys(result[0]); + resultKeys.forEach(function (key) { + expect(keys.indexOf(key) !== -1).to.equal(true); + }); + }) + }); +}); From c3c04f55448625012d7465f98c53a86c37e4a843 Mon Sep 17 00:00:00 2001 From: harpere Date: Tue, 8 Sep 2020 13:18:20 -0400 Subject: [PATCH 11/34] minor validation update to consentManagement.js (#5701) Co-authored-by: Eric Harper --- modules/consentManagement.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/consentManagement.js b/modules/consentManagement.js index 19fbe827eb1..f44fde0554d 100644 --- a/modules/consentManagement.js +++ b/modules/consentManagement.js @@ -383,7 +383,7 @@ function storeConsentData(cmpConsentObject) { vendorData: (cmpConsentObject) || undefined, gdprApplies: cmpConsentObject && typeof cmpConsentObject.gdprApplies === 'boolean' ? cmpConsentObject.gdprApplies : gdprScope }; - if (cmpConsentObject.addtlConsent && utils.isStr(cmpConsentObject.addtlConsent)) { + if (cmpConsentObject && cmpConsentObject.addtlConsent && utils.isStr(cmpConsentObject.addtlConsent)) { consentData.addtlConsent = cmpConsentObject.addtlConsent; }; } From 44a3797692d2e47a381f29d9234db321b21d5fe3 Mon Sep 17 00:00:00 2001 From: Vladyslav Laktionov Date: Tue, 8 Sep 2020 20:20:32 +0300 Subject: [PATCH 12/34] New Bid Adapter: decenterads (#5711) * add new version decenteradsBidAdapter.js * add new version decenteradsBidAdapter_spec.js * add .js to import * replace method getWindowLocation * replace urls Co-authored-by: vlad --- modules/decenteradsBidAdapter.js | 90 ++++++++ .../modules/decenteradsBidAdapter_spec.js | 207 ++++++++++++++++++ 2 files changed, 297 insertions(+) create mode 100644 modules/decenteradsBidAdapter.js create mode 100644 test/spec/modules/decenteradsBidAdapter_spec.js diff --git a/modules/decenteradsBidAdapter.js b/modules/decenteradsBidAdapter.js new file mode 100644 index 00000000000..823a59a3768 --- /dev/null +++ b/modules/decenteradsBidAdapter.js @@ -0,0 +1,90 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js' +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js' +import * as utils from '../src/utils.js' + +const BIDDER_CODE = 'decenterads' +const URL = 'https://supply.decenterads.com/?c=o&m=multi' +const URL_SYNC = 'https://supply.decenterads.com/?c=o&m=cookie' + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: function (opts) { + return Boolean(opts.bidId && opts.params && !isNaN(opts.params.placementId)) + }, + + buildRequests: function (validBidRequests) { + validBidRequests = validBidRequests || [] + let winTop = window + try { + window.top.location.toString() + winTop = window.top + } catch (e) { utils.logMessage(e) } + + const location = utils.getWindowLocation() + const placements = [] + + for (let i = 0; i < validBidRequests.length; i++) { + const p = validBidRequests[i] + + placements.push({ + placementId: p.params.placementId, + bidId: p.bidId, + traffic: p.params.traffic || BANNER + }) + } + + return { + method: 'POST', + url: URL, + data: { + deviceWidth: winTop.screen.width, + deviceHeight: winTop.screen.height, + language: (navigator && navigator.language) ? navigator.language : '', + secure: +(location.protocol === 'https:'), + host: location.hostname, + page: location.pathname, + placements: placements + } + } + }, + + interpretResponse: function (opts) { + const body = opts.body + const response = [] + + for (let i = 0; i < body.length; i++) { + const item = body[i] + if (isBidResponseValid(item)) { + delete item.mediaType + response.push(item) + } + } + + return response + }, + + getUserSyncs: function (syncOptions, serverResponses) { + return [{ type: 'image', url: URL_SYNC }] + } +} + +registerBidder(spec) + +function isBidResponseValid (bid) { + if (!bid.requestId || !bid.cpm || !bid.creativeId || + !bid.ttl || !bid.currency) { + return false + } + switch (bid['mediaType']) { + case BANNER: + return Boolean(bid.width && bid.height && bid.ad) + case VIDEO: + return Boolean(bid.vastUrl) + case NATIVE: + return Boolean(bid.title && bid.image && bid.impressionTrackers) + default: + return false + } +} diff --git a/test/spec/modules/decenteradsBidAdapter_spec.js b/test/spec/modules/decenteradsBidAdapter_spec.js new file mode 100644 index 00000000000..257094cae3a --- /dev/null +++ b/test/spec/modules/decenteradsBidAdapter_spec.js @@ -0,0 +1,207 @@ +import { expect } from 'chai' +import { spec } from '../../../modules/decenteradsBidAdapter.js' +import { deepStrictEqual, notEqual, ok, strictEqual } from 'assert' + +describe('DecenteradsAdapter', () => { + const bid = { + bidId: '9ec5b177515ee2e5', + bidder: 'decenterads', + params: { + placementId: 0, + traffic: 'banner' + } + } + + describe('isBidRequestValid', () => { + it('Should return true if there are bidId, params and placementId parameters present', () => { + strictEqual(true, spec.isBidRequestValid(bid)) + }) + + it('Should return false if at least one of parameters is not present', () => { + const b = { ...bid } + delete b.params.placementId + strictEqual(false, spec.isBidRequestValid(b)) + }) + }) + + describe('buildRequests', () => { + const serverRequest = spec.buildRequests([bid]) + + it('Creates a ServerRequest object with method, URL and data', () => { + ok(serverRequest) + ok(serverRequest.method) + ok(serverRequest.url) + ok(serverRequest.data) + }) + + it('Returns POST method', () => { + strictEqual('POST', serverRequest.method) + }) + + it('Returns valid URL', () => { + strictEqual('https://supply.decenterads.com/?c=o&m=multi', serverRequest.url) + }) + + it('Returns valid data if array of bids is valid', () => { + const { data } = serverRequest + strictEqual('object', typeof data) + deepStrictEqual(['deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements'], Object.keys(data)) + strictEqual('number', typeof data.deviceWidth) + strictEqual('number', typeof data.deviceHeight) + strictEqual('string', typeof data.language) + strictEqual('string', typeof data.host) + strictEqual('string', typeof data.page) + notEqual(-1, [0, 1].indexOf(data.secure)) + + const placement = data.placements[0] + deepStrictEqual(['placementId', 'bidId', 'traffic'], Object.keys(placement)) + strictEqual(0, placement.placementId) + strictEqual('9ec5b177515ee2e5', placement.bidId) + strictEqual('banner', placement.traffic) + }) + + it('Returns empty data if no valid requests are passed', () => { + const { placements } = spec.buildRequests([]).data + + expect(spec.buildRequests([]).data.placements).to.be.an('array') + strictEqual(0, placements.length) + }) + }) + + describe('interpretResponse', () => { + const validData = [ + { + body: [{ + mediaType: 'banner', + width: 300, + height: 250, + cpm: 0.4, + ad: 'Test', + requestId: '9ec5b177515ee2e5', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }, + { + body: [{ + vastUrl: 'decenterads.com', + mediaType: 'video', + cpm: 0.5, + requestId: '9ec5b177515ee2e5', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }, + { + body: [{ + mediaType: 'native', + clickUrl: 'decenterads.com', + title: 'Test', + image: 'decenterads.com', + creativeId: '2', + impressionTrackers: ['decenterads.com'], + ttl: 120, + cpm: 0.4, + requestId: '9ec5b177515ee2e5', + netRevenue: true, + currency: 'USD', + }] + } + ] + + for (const obj of validData) { + const { mediaType } = obj.body[0] + + it(`Should interpret ${mediaType} response`, () => { + const response = spec.interpretResponse(obj) + + expect(response).to.be.an('array') + strictEqual(1, response.length) + + const copy = { ...obj.body[0] } + delete copy.mediaType + deepStrictEqual(copy, response[0]) + }) + } + + const invalidData = [ + { + body: [{ + width: 300, + cpm: 0.4, + ad: 'Test', + requestId: '9ec5b177515ee2e5', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }, + { + body: [{ + mediaType: 'video', + cpm: 0.5, + requestId: '9ec5b177515ee2e5', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }, + { + body: [{ + mediaType: 'native', + clickUrl: 'decenterads.com', + title: 'Test', + impressionTrackers: ['decenterads.com'], + ttl: 120, + requestId: '9ec5b177515ee2e5', + creativeId: '2', + netRevenue: true, + currency: 'USD', + }] + } + ] + + for (const obj of invalidData) { + const { mediaType } = obj.body[0] + + it(`Should return an empty array if invalid ${mediaType} response is passed `, () => { + const response = spec.interpretResponse(obj) + + expect(response).to.be.an('array') + strictEqual(0, response.length) + }) + } + + it('Should return an empty array if invalid response is passed', () => { + const response = spec.interpretResponse({ + body: [{ + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }) + + expect(response).to.be.an('array') + strictEqual(0, response.length) + }) + }) + + describe('getUserSyncs', () => { + it('Returns valid URL and type', () => { + const expectedResult = [{ type: 'image', url: 'https://supply.decenterads.com/?c=o&m=cookie' }] + deepStrictEqual(expectedResult, spec.getUserSyncs()) + }) + }) +}) From 1676c76c4d47833fbb6408b2d1261bd485238966 Mon Sep 17 00:00:00 2001 From: "Isaac A. Dettman" Date: Tue, 8 Sep 2020 10:29:22 -0700 Subject: [PATCH 13/34] fix GPT Pre-Auction PBS path (#5650) * Add microadBidAdapter * Remove unnecessary encodeURIComponent from microadBidAdapter * Submit Advangelists Prebid Adapter * Submit Advangelists Prebid Adapter 1.1 * Correct procudtion endpoint for prebid * analytics update with wrapper name * reverted error merge * update changed default value of netRevenue to true * Re-add rubicon analytics without deprecated getTopWindowUrl util * Cache referrer on auction_init instead of bid_requested * merged remote master changes * updated unit tests for object path changes * updated rp bid adapter unit tests for GAM object path changes * updated naming and changed iterator to use arrow syntax * continue renaming and cleanup Co-authored-by: nakamoto Co-authored-by: Chandra Prakash Co-authored-by: Eric Harper Co-authored-by: TJ Eastmond Co-authored-by: Mark Monday Co-authored-by: msm0504 <51493331+msm0504@users.noreply.github.com> --- modules/prebidServerBidAdapter/index.js | 14 ++++++++------ modules/rubiconBidAdapter.js | 14 ++++++++------ .../modules/prebidServerBidAdapter_spec.js | 19 +++++++++++-------- test/spec/modules/rubiconBidAdapter_spec.js | 8 +++++--- 4 files changed, 32 insertions(+), 23 deletions(-) diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index b3d559d956f..a7a3199675d 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -551,13 +551,15 @@ const OPEN_RTB_PROTOCOL = { } /** - * GAM Ad Unit - * @type {(string|undefined)} + * Copy GAM AdUnit and Name to imp */ - const gamAdUnit = utils.deepAccess(adUnit, 'fpd.context.adServer.adSlot'); - if (typeof gamAdUnit === 'string' && gamAdUnit) { - utils.deepSetValue(imp, 'ext.context.data.adslot', gamAdUnit); - } + ['name', 'adSlot'].forEach(name => { + /** @type {(string|undefined)} */ + const value = utils.deepAccess(adUnit, `fpd.context.adserver.${name}`); + if (typeof value === 'string' && value) { + utils.deepSetValue(imp, `ext.context.data.adserver.${name.toLowerCase()}`, value); + } + }); Object.assign(imp, mediaTypes); diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index da23cca62d1..979cc430f15 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -308,13 +308,15 @@ export const spec = { } /** - * GAM Ad Unit - * @type {(string|undefined)} + * Copy GAM AdUnit and Name to imp */ - const gamAdUnit = utils.deepAccess(bidRequest, 'fpd.context.adServer.adSlot'); - if (typeof gamAdUnit === 'string' && gamAdUnit) { - utils.deepSetValue(data.imp[0].ext, 'context.data.adslot', gamAdUnit); - } + ['name', 'adSlot'].forEach(name => { + /** @type {(string|undefined)} */ + const value = utils.deepAccess(bidRequest, `fpd.context.adserver.${name}`); + if (typeof value === 'string' && value) { + utils.deepSetValue(data.imp[0].ext, `context.data.adserver.${name.toLowerCase()}`, value); + } + }); // if storedAuctionResponse has been set, pass SRID if (bidRequest.storedAuctionResponse) { diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index 5abe068c100..a300a10d31b 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -1516,7 +1516,7 @@ describe('S2S Adapter', function () { }); describe('GAM ad unit config', function () { - it('should not send \"imp.ext.context.data.adslot\" if \"fpd.context\" is undefined', function () { + it('should not send \"imp.ext.context.data.adserver.adslot\" if \"fpd.context\" is undefined', function () { const ortb2Config = utils.deepClone(CONFIG); ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; const consentConfig = { s2sConfig: ortb2Config }; @@ -1531,7 +1531,7 @@ describe('S2S Adapter', function () { expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.context.data.adslot'); }); - it('should not send \"imp.ext.context.data.adslot\" if \"fpd.context.adServer.adSlot\" is undefined', function () { + it('should not send \"imp.ext.context.data.adserver.adslot\" if \"fpd.context.adserver.adSlot\" is undefined', function () { const ortb2Config = utils.deepClone(CONFIG); ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; const consentConfig = { s2sConfig: ortb2Config }; @@ -1547,7 +1547,7 @@ describe('S2S Adapter', function () { expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.context.data.adslot'); }); - it('should not send \"imp.ext.context.data.adslot\" if \"fpd.context.adServer.adSlot\" is empty string', function () { + it('should not send \"imp.ext.context.data.adserver.adslot\" if \"fpd.context.adserver.adSlot\" is empty string', function () { const ortb2Config = utils.deepClone(CONFIG); ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; const consentConfig = { s2sConfig: ortb2Config }; @@ -1569,7 +1569,7 @@ describe('S2S Adapter', function () { expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.context.data.adslot'); }); - it('should send \"imp.ext.context.data.adslot\" if \"fpd.context.adServer.adSlot\" value is a non-empty string', function () { + it('should send both \"adslot\" and \"name\" from \"imp.ext.context.data.adserver\" if \"fpd.context.adserver.adSlot\" and \"fpd.context.adserver.name\" values are non-empty strings', function () { const ortb2Config = utils.deepClone(CONFIG); ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; const consentConfig = { s2sConfig: ortb2Config }; @@ -1577,8 +1577,9 @@ describe('S2S Adapter', function () { const bidRequest = utils.deepClone(REQUEST); bidRequest.ad_units[0].fpd = { context: { - adServer: { - adSlot: '/a/b/c' + adserver: { + adSlot: '/a/b/c', + name: 'adserverName1' } } }; @@ -1588,8 +1589,10 @@ describe('S2S Adapter', function () { expect(parsedRequestBody.imp).to.be.a('array'); expect(parsedRequestBody.imp[0]).to.be.a('object'); - expect(parsedRequestBody.imp[0]).to.have.deep.nested.property('ext.context.data.adslot'); - expect(parsedRequestBody.imp[0].ext.context.data.adslot).to.equal('/a/b/c'); + expect(parsedRequestBody.imp[0]).to.have.deep.nested.property('ext.context.data.adserver.adslot'); + expect(parsedRequestBody.imp[0]).to.have.deep.nested.property('ext.context.data.adserver.name'); + expect(parsedRequestBody.imp[0].ext.context.data.adserver.adslot).to.equal('/a/b/c'); + expect(parsedRequestBody.imp[0].ext.context.data.adserver.name).to.equal('adserverName1'); }); }); }); diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index a2b554c1f5d..c5c0f644643 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -2037,8 +2037,9 @@ describe('the rubicon adapter', function () { createVideoBidderRequest(); bidderRequest.bids[0].fpd = { context: { - adServer: { - adSlot: '1234567890' + adserver: { + adSlot: '1234567890', + name: 'adServerName1' } } }; @@ -2048,7 +2049,8 @@ describe('the rubicon adapter', function () { ); const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.imp[0].ext.context.data.adslot).to.equal('1234567890'); + expect(request.data.imp[0].ext.context.data.adserver.adslot).to.equal('1234567890'); + expect(request.data.imp[0].ext.context.data.adserver.name).to.equal('adServerName1'); }); it('should use the integration type provided in the config instead of the default', () => { From 22ce19ff42ff25c51502c32a6a2a1079a5b76a38 Mon Sep 17 00:00:00 2001 From: Scott Menzer Date: Tue, 8 Sep 2020 19:34:07 +0200 Subject: [PATCH 14/34] Increment pre version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 846a35e4127..7ad0781d648 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.7.0", + "version": "4.8.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 0d3c632622d699d34b9b897012e57319fef0c343 Mon Sep 17 00:00:00 2001 From: Estavillo Date: Tue, 8 Sep 2020 22:38:06 +0200 Subject: [PATCH 15/34] GumGumBidAdapter: Add support for multiple sizes (#5626) add UT UT Co-authored-by: Estavillo --- modules/gumgumBidAdapter.js | 11 ++++++++++ test/spec/modules/gumgumBidAdapter_spec.js | 25 ++++++++++++++++++++-- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/modules/gumgumBidAdapter.js b/modules/gumgumBidAdapter.js index ebeb46e3c44..f8e17e0fe71 100644 --- a/modules/gumgumBidAdapter.js +++ b/modules/gumgumBidAdapter.js @@ -275,6 +275,17 @@ function buildRequests (validBidRequests, bidderRequest) { } if (params.inSlot) { data.si = parseInt(params.inSlot, 10); + // check for sizes and type + if (params.sizes && Array.isArray(params.sizes)) { + const bf = params.sizes.reduce(function(r, i) { + // only push if it's an array of length 2 + if (Array.isArray(i) && i.length === 2) { + r.push(`${i[0]}x${i[1]}`); + } + return r; + }, []); + data.bf = bf.toString(); + } data.pi = 3; } if (params.ICV) { diff --git a/test/spec/modules/gumgumBidAdapter_spec.js b/test/spec/modules/gumgumBidAdapter_spec.js index af929f437da..cf672d89e22 100644 --- a/test/spec/modules/gumgumBidAdapter_spec.js +++ b/test/spec/modules/gumgumBidAdapter_spec.js @@ -46,6 +46,17 @@ describe('gumgumAdapter', function () { expect(spec.isBidRequestValid(bid)).to.equal(true); }); + it('should return true when inslot sends sizes and trackingid', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'inSlot': '789', + 'sizes': [[0, 1], [2, 3], [4, 5], [6, 7]] + }; + + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + it('should return false when no unit type is specified', function () { let bid = Object.assign({}, bid); delete bid.params; @@ -81,6 +92,7 @@ describe('gumgumAdapter', function () { }); describe('buildRequests', function () { + let sizesArray = [[300, 250], [300, 600]]; let bidRequests = [ { 'bidder': 'gumgum', @@ -88,7 +100,7 @@ describe('gumgumAdapter', function () { 'inSlot': '9' }, 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [300, 600]], + 'sizes': sizesArray, 'bidId': '30b31c1838de1e', 'schain': { 'ver': '1.0', @@ -132,7 +144,16 @@ describe('gumgumAdapter', function () { const bidRequest = spec.buildRequests([request])[0]; expect(bidRequest.sizes).to.equal(vidMediaTypes.video.playerSize); }); - + it('should handle multiple sizes for inslot', function () { + const request = Object.assign({}, bidRequests[0]); + delete request.params; + request.params = { + 'inSlot': '123', + 'sizes': [[0, 1], [0, 2]] + }; + const bidRequest = spec.buildRequests([request])[0]; + expect(bidRequest.data.bf).to.equal('0x1,0x2'); + }); describe('floorModule', function () { const floorTestData = { 'currency': 'USD', From 05283d0a06f897c0cda476383dc0408afff7330c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=A1clav=20Proch=C3=A1zka?= Date: Wed, 9 Sep 2020 03:10:29 +0200 Subject: [PATCH 16/34] Add host to gulpfile (#5710) * Add host to gulpfile * Edit arg.host to FAKE_SERVER_HOST Co-authored-by: VasekProchazka --- gulpfile.js | 1 + 1 file changed, 1 insertion(+) diff --git a/gulpfile.js b/gulpfile.js index 64152baa7ba..879e34ae588 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -110,6 +110,7 @@ function watch(done) { connect.server({ https: argv.https, port: port, + host: FAKE_SERVER_HOST, root: './', livereload: true }); From b96c1ccd3b9a718013539db21534c65daf3bdb32 Mon Sep 17 00:00:00 2001 From: frstua Date: Wed, 9 Sep 2020 04:18:10 +0300 Subject: [PATCH 17/34] Move test and publisherId parameters to bidder specific config (#5692) Co-authored-by: Yevhenii Tykhostup --- modules/apstreamBidAdapter.js | 19 ++++++++++------- modules/apstreamBidAdapter.md | 14 +++++++++++++ test/spec/modules/apstreamBidAdapter_spec.js | 22 ++++++++++++++++++++ 3 files changed, 48 insertions(+), 7 deletions(-) diff --git a/modules/apstreamBidAdapter.js b/modules/apstreamBidAdapter.js index 324c125f5ef..4fb89b9c720 100644 --- a/modules/apstreamBidAdapter.js +++ b/modules/apstreamBidAdapter.js @@ -378,23 +378,27 @@ function getBids(bids) { function getEndpointsGroups(bidRequests) { let endpoints = []; const getEndpoint = bid => { - if (bid.params.test) { - return `https://mock-bapi.userreport.com/v2/${bid.params.publisherId}/bid`; + const publisherId = bid.params.publisherId || config.getConfig('apstream.publisherId'); + const isTestConfig = bid.params.test || config.getConfig('apstream.test'); + + if (isTestConfig) { + return `https://mock-bapi.userreport.com/v2/${publisherId}/bid`; } if (bid.params.endpoint) { - return `${bid.params.endpoint}${bid.params.publisherId}/bid`; + return `${bid.params.endpoint}${publisherId}/bid`; } - return `https://bapi.userreport.com/v2/${bid.params.publisherId}/bid`; + return `https://bapi.userreport.com/v2/${publisherId}/bid`; } bidRequests.forEach(bid => { - const exist = endpoints.filter(item => item.endpoint.indexOf(bid.params.endpoint) > -1)[0]; + const endpoint = getEndpoint(bid); + const exist = endpoints.filter(item => item.endpoint.indexOf(endpoint) > -1)[0]; if (exist) { exist.bids.push(bid); } else { endpoints.push({ - endpoint: getEndpoint(bid), + endpoint: endpoint, bids: [bid] }); } @@ -404,7 +408,8 @@ function getEndpointsGroups(bidRequests) { } function isBidRequestValid(bid) { - const isPublisherIdExist = !!bid.params.publisherId; + const publisherId = config.getConfig('apstream.publisherId'); + const isPublisherIdExist = !!(publisherId || bid.params.publisherId); const isOneMediaType = Object.keys(bid.mediaTypes).length === 1; return isPublisherIdExist && isOneMediaType; diff --git a/modules/apstreamBidAdapter.md b/modules/apstreamBidAdapter.md index e528307a003..6b87b33489a 100644 --- a/modules/apstreamBidAdapter.md +++ b/modules/apstreamBidAdapter.md @@ -95,3 +95,17 @@ To disable DSU use config option: } }); ``` + +To set `test` and `publisherId` parameters globally use config options (it can be overrided if set in specific bid): + +``` +pbjs.setBidderConfig({ + bidders: ["apstream"], + config: { + appstream: { + publisherId: '1234 + test: true + } + } +}); +``` diff --git a/test/spec/modules/apstreamBidAdapter_spec.js b/test/spec/modules/apstreamBidAdapter_spec.js index c6546a3bd83..e640c009989 100644 --- a/test/spec/modules/apstreamBidAdapter_spec.js +++ b/test/spec/modules/apstreamBidAdapter_spec.js @@ -31,6 +31,22 @@ describe('AP Stream adapter', function() { } }; + let mockConfig; + beforeEach(function () { + mockConfig = { + apstream: { + publisherId: '4321' + } + }; + sinon.stub(config, 'getConfig').callsFake((key) => { + return utils.deepAccess(mockConfig, key); + }); + }); + + afterEach(function () { + config.getConfig.restore(); + }); + it('should return true when publisherId is configured and one media type', function() { bid.params.publisherId = '1234'; assert(spec.isBidRequestValid(bid)) @@ -40,6 +56,12 @@ describe('AP Stream adapter', function() { bid.mediaTypes.video = {sizes: [300, 250]}; assert.isFalse(spec.isBidRequestValid(bid)) }); + + it('should return true when publisherId is configured via config', function() { + delete bid.mediaTypes.video; + delete bid.params.publisherId; + assert.isTrue(spec.isBidRequestValid(bid)) + }); }); describe('buildRequests', function() { From 3726fd6f25981cdbc7b0c57ac4fefc871cf99584 Mon Sep 17 00:00:00 2001 From: Shikhar Sharma <36563196+shikhar-dev-proj@users.noreply.github.com> Date: Wed, 9 Sep 2020 12:58:55 +0530 Subject: [PATCH 18/34] fix userId_example.html (#5606) --- integrationExamples/gpt/userId_example.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/integrationExamples/gpt/userId_example.html b/integrationExamples/gpt/userId_example.html index 9307bc91456..8115e60fcd1 100644 --- a/integrationExamples/gpt/userId_example.html +++ b/integrationExamples/gpt/userId_example.html @@ -84,6 +84,9 @@ { code: 'test-div', sizes: [[300,250],[300,600],[728,90]], + mediaTypes: { + banner: {} + }, bids: [ { bidder: 'rubicon', From 3c9e42fd3038c87ec645c01c12e4199c6d92c9b5 Mon Sep 17 00:00:00 2001 From: Rahul Shandilya <67756716+c3p-0@users.noreply.github.com> Date: Wed, 9 Sep 2020 13:06:28 +0530 Subject: [PATCH 19/34] MediaNet SChain Support (#5685) --- modules/medianetBidAdapter.js | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/modules/medianetBidAdapter.js b/modules/medianetBidAdapter.js index 9ff0192eab4..5decaa148e3 100644 --- a/modules/medianetBidAdapter.js +++ b/modules/medianetBidAdapter.js @@ -131,11 +131,16 @@ function getCoordinates(id) { return null; } -function extParams(params, gdpr, uspConsent, userId) { - let windowSize = spec.getWindowSize(); - let gdprApplies = !!(gdpr && gdpr.gdprApplies); - let uspApplies = !!(uspConsent); - let coppaApplies = !!(config.getConfig('coppa')); +function extParams(bidRequest, bidderRequests) { + const params = utils.deepAccess(bidRequest, 'params'); + const gdpr = utils.deepAccess(bidderRequests, 'gdprConsent'); + const uspConsent = utils.deepAccess(bidderRequests, 'uspConsent'); + const userId = utils.deepAccess(bidRequest, 'userId'); + const sChain = utils.deepAccess(bidRequest, 'schain') || {}; + const windowSize = spec.getWindowSize(); + const gdprApplies = !!(gdpr && gdpr.gdprApplies); + const uspApplies = !!(uspConsent); + const coppaApplies = !!(config.getConfig('coppa')); return Object.assign({}, { customer_id: params.cid }, { prebid_version: $$PREBID_GLOBAL$$.version }, @@ -146,7 +151,8 @@ function extParams(params, gdpr, uspConsent, userId) { {coppa_applies: coppaApplies}, windowSize.w !== -1 && windowSize.h !== -1 && { screen: windowSize }, userId && { user_id: userId }, - $$PREBID_GLOBAL$$.medianetGlobals.analyticsEnabled && { analytics: true } + $$PREBID_GLOBAL$$.medianetGlobals.analyticsEnabled && { analytics: true }, + !utils.isEmpty(sChain) && {schain: sChain} ); } @@ -254,7 +260,7 @@ function getBidderURL(cid) { function generatePayload(bidRequests, bidderRequests) { return { site: siteDetails(bidRequests[0].params.site), - ext: extParams(bidRequests[0].params, bidderRequests.gdprConsent, bidderRequests.uspConsent, bidRequests[0].userId), + ext: extParams(bidRequests[0], bidderRequests), id: bidRequests[0].auctionId, imp: bidRequests.map(request => slotParams(request)), tmax: bidderRequests.timeout || config.getConfig('bidderTimeout') From b0714570bdaea441a5df1ed851458876a6751aa6 Mon Sep 17 00:00:00 2001 From: Stephen Johnston Date: Wed, 9 Sep 2020 05:41:10 -0400 Subject: [PATCH 20/34] PubWise.io Analytics Module Update - SPOT Support, Module Rules & Minor Features (#5677) * updates to bring module up to current module rules, lints, etc. also add activationId and improve tests * fix eslint error * Fix IE11 Includes Check Failing Tests * revert debug setting * updates to fix PR review issues * updates to fix default params handling --- modules/pubwiseAnalyticsAdapter.js | 291 +++++++++++++++--- .../modules/pubwiseAnalyticsAdapter_spec.js | 159 ++++++++-- 2 files changed, 386 insertions(+), 64 deletions(-) diff --git a/modules/pubwiseAnalyticsAdapter.js b/modules/pubwiseAnalyticsAdapter.js index 915aeb58f99..74b56c21a2b 100644 --- a/modules/pubwiseAnalyticsAdapter.js +++ b/modules/pubwiseAnalyticsAdapter.js @@ -4,7 +4,6 @@ import adapterManager from '../src/adapterManager.js'; import CONSTANTS from '../src/constants.json'; import { getStorageManager } from '../src/storageManager.js'; const utils = require('../src/utils.js'); - const storage = getStorageManager(); /**** @@ -17,30 +16,54 @@ const storage = getStorageManager(); pbjs.enableAnalytics({ provider: 'pubwise', options: { - site: 'test-test-test-test', - endpoint: 'https://api.pubwise.io/api/v4/event/add/', + site: 'b1ccf317-a6fc-428d-ba69-0c9c208aa61c' } }); - */ + +Changes in 4.0 Version +4.0.1 - Initial Version for Prebid 4.x, adds activationId, adds additiona testing, removes prebid global in favor of a prebid.version const +4.0.2 - Updates to include dedicated default site to keep everything from getting rate limited + +*/ const analyticsType = 'endpoint'; -const analyticsName = 'PubWise Analytics: '; -let defaultUrl = 'https://api.pubwise.io/api/v4/event/default/'; -let pubwiseVersion = '3.0'; -let pubwiseSchema = 'AVOCET'; -let configOptions = {site: '', endpoint: 'https://api.pubwise.io/api/v4/event/default/', debug: ''}; +const analyticsName = 'PubWise:'; +const prebidVersion = '$prebid.version$'; +let pubwiseVersion = '4.0.1'; +let configOptions = {site: '', endpoint: 'https://api.pubwise.io/api/v5/event/add/', debug: null}; let pwAnalyticsEnabled = false; let utmKeys = {utm_source: '', utm_medium: '', utm_campaign: '', utm_term: '', utm_content: ''}; +let sessionData = {sessionId: '', activationId: ''}; +let pwNamespace = 'pubwise'; +let pwEvents = []; +let metaData = {}; +let auctionEnded = false; +let sessTimeout = 60 * 30 * 1000; // 30 minutes, G Analytics default session length +let sessName = 'sess_id'; +let sessTimeoutName = 'sess_timeout'; -function markEnabled() { - utils.logInfo(`${analyticsName}Enabled`, configOptions); - pwAnalyticsEnabled = true; +function enrichWithSessionInfo(dataBag) { + try { + // eslint-disable-next-line + // console.log(sessionData); + dataBag['session_id'] = sessionData.sessionId; + dataBag['activation_id'] = sessionData.activationId; + } catch (e) { + dataBag['error_sess'] = 1; + } + + return dataBag; } function enrichWithMetrics(dataBag) { try { + if (window.PREBID_TIMEOUT) { + dataBag['target_timeout'] = window.PREBID_TIMEOUT; + } else { + dataBag['target_timeout'] = 'NA'; + } dataBag['pw_version'] = pubwiseVersion; - dataBag['pbjs_version'] = $$PREBID_GLOBAL$$.version; + dataBag['pbjs_version'] = prebidVersion; dataBag['debug'] = configOptions.debug; } catch (e) { dataBag['error_metric'] = 1; @@ -54,7 +77,7 @@ function enrichWithUTM(dataBag) { try { for (let prop in utmKeys) { utmKeys[prop] = utils.getParameterByName(prop); - if (utmKeys[prop] != '') { + if (utmKeys[prop]) { newUtm = true; dataBag[prop] = utmKeys[prop]; } @@ -62,64 +85,246 @@ function enrichWithUTM(dataBag) { if (newUtm === false) { for (let prop in utmKeys) { - let itemValue = storage.getDataFromLocalStorage(`pw-${prop}`); - if (itemValue.length !== 0) { + let itemValue = storage.getDataFromLocalStorage(setNamespace(prop)); + if (itemValue !== null && typeof itemValue !== 'undefined' && itemValue.length !== 0) { dataBag[prop] = itemValue; } } } else { for (let prop in utmKeys) { - storage.setDataInLocalStorage(`pw-${prop}`, utmKeys[prop]); + storage.setDataInLocalStorage(setNamespace(prop), utmKeys[prop]); } } } catch (e) { - utils.logInfo(`${analyticsName}Error`, e); + pwInfo(`Error`, e); dataBag['error_utm'] = 1; } return dataBag; } -function sendEvent(eventType, data) { - utils.logInfo(`${analyticsName}Event ${eventType} ${pwAnalyticsEnabled}`, data); +function expireUtmData() { + pwInfo(`Session Expiring UTM Data`); + for (let prop in utmKeys) { + storage.removeDataFromLocalStorage(setNamespace(prop)); + } +} + +function enrichWithCustomSegments(dataBag) { + // c_script_type: '', c_slot1: '', c_slot2: '', c_slot3: '', c_slot4: '' + if (configOptions.custom) { + if (configOptions.custom.c_script_type) { + dataBag['c_script_type'] = configOptions.custom.c_script_type; + } + + if (configOptions.custom.c_host) { + dataBag['c_host'] = configOptions.custom.c_host; + } + + if (configOptions.custom.c_slot1) { + dataBag['c_slot1'] = configOptions.custom.c_slot1; + } + + if (configOptions.custom.c_slot2) { + dataBag['c_slot2'] = configOptions.custom.c_slot2; + } + + if (configOptions.custom.c_slot3) { + dataBag['c_slot3'] = configOptions.custom.c_slot3; + } + + if (configOptions.custom.c_slot4) { + dataBag['c_slot4'] = configOptions.custom.c_slot4; + } + } + + return dataBag; +} + +function setNamespace(itemText) { + return pwNamespace.concat('_' + itemText); +} + +function localStorageSessTimeoutName() { + return setNamespace(sessTimeoutName); +} + +function localStorageSessName() { + return setNamespace(sessName); +} + +function extendUserSessionTimeout() { + storage.setDataInLocalStorage(localStorageSessTimeoutName(), Date.now().toString()); +} + +function userSessionID() { + return storage.getDataFromLocalStorage(localStorageSessName()) ? localStorage.getItem(localStorageSessName()) : ''; +} + +function sessionExpired() { + let sessLastTime = storage.getDataFromLocalStorage(localStorageSessTimeoutName()); + return (Date.now() - parseInt(sessLastTime)) > sessTimeout; +} + +function flushEvents() { + if (pwEvents.length > 0) { + let dataBag = {metaData: metaData, eventList: pwEvents.splice(0)}; // put all the events together with the metadata and send + ajax(configOptions.endpoint, (result) => pwInfo(`Result`, result), JSON.stringify(dataBag)); + } +} + +function isIngestedEvent(eventType) { + const ingested = [ + CONSTANTS.EVENTS.AUCTION_INIT, + CONSTANTS.EVENTS.BID_REQUESTED, + CONSTANTS.EVENTS.BID_RESPONSE, + CONSTANTS.EVENTS.BID_WON, + CONSTANTS.EVENTS.BID_TIMEOUT, + CONSTANTS.EVENTS.AD_RENDER_FAILED, + CONSTANTS.EVENTS.TCF2_ENFORCEMENT + ]; + return ingested.indexOf(eventType) !== -1; +} - // put the typical items in the data bag - let dataBag = { - eventType: eventType, - args: data, - target_site: configOptions.site, - pubwiseSchema: pubwiseSchema, - debug: configOptions.debug ? 1 : 0, - }; +function markEnabled() { + pwInfo(`Enabled`, configOptions); + pwAnalyticsEnabled = true; + setInterval(flushEvents, 100); +} + +function pwInfo(info, context) { + utils.logInfo(`${analyticsName} ` + info, context); +} - dataBag = enrichWithMetrics(dataBag); - // for certain events, track additional info - if (eventType == CONSTANTS.EVENTS.AUCTION_INIT) { - dataBag = enrichWithUTM(dataBag); +function filterBidResponse(data) { + let modified = Object.assign({}, data); + // clean up some properties we don't track in public version + if (typeof modified.ad !== 'undefined') { + modified.ad = ''; } + if (typeof modified.adUrl !== 'undefined') { + modified.adUrl = ''; + } + if (typeof modified.adserverTargeting !== 'undefined') { + modified.adserverTargeting = ''; + } + if (typeof modified.ts !== 'undefined') { + modified.ts = ''; + } + // clean up a property to make simpler + if (typeof modified.statusMessage !== 'undefined' && modified.statusMessage === 'Bid returned empty or error response') { + modified.statusMessage = 'eoe'; + } + modified.auctionEnded = auctionEnded; + return modified; +} - ajax(configOptions.endpoint, (result) => utils.logInfo(`${analyticsName}Result`, result), JSON.stringify(dataBag)); +function filterAuctionInit(data) { + let modified = Object.assign({}, data); + + modified.refererInfo = {}; + // handle clean referrer, we only need one + if (typeof modified.bidderRequests !== 'undefined' && typeof modified.bidderRequests[0] !== 'undefined' && typeof modified.bidderRequests[0].refererInfo !== 'undefined') { + modified.refererInfo = modified.bidderRequests[0].refererInfo; + } + + if (typeof modified.adUnitCodes !== 'undefined') { + delete modified.adUnitCodes; + } + if (typeof modified.adUnits !== 'undefined') { + delete modified.adUnits; + } + if (typeof modified.bidderRequests !== 'undefined') { + delete modified.bidderRequests; + } + if (typeof modified.bidsReceived !== 'undefined') { + delete modified.bidsReceived; + } + if (typeof modified.config !== 'undefined') { + delete modified.config; + } + if (typeof modified.noBids !== 'undefined') { + delete modified.noBids; + } + if (typeof modified.winningBids !== 'undefined') { + delete modified.winningBids; + } + + return modified; } -let pubwiseAnalytics = Object.assign(adapter( - { - defaultUrl, - analyticsType - }), -{ +let pubwiseAnalytics = Object.assign(adapter({analyticsType}), { // Override AnalyticsAdapter functions by supplying custom methods track({eventType, args}) { - sendEvent(eventType, args); + this.handleEvent(eventType, args); } }); +pubwiseAnalytics.handleEvent = function(eventType, data) { + // we log most events, but some are information + if (isIngestedEvent(eventType)) { + pwInfo(`Emitting Event ${eventType} ${pwAnalyticsEnabled}`, data); + + // record metadata + metaData = { + target_site: configOptions.site, + debug: configOptions.debug ? 1 : 0, + }; + metaData = enrichWithSessionInfo(metaData); + metaData = enrichWithMetrics(metaData); + metaData = enrichWithUTM(metaData); + metaData = enrichWithCustomSegments(metaData); + + // add data on init to the metadata container + if (eventType === CONSTANTS.EVENTS.AUCTION_INIT) { + data = filterAuctionInit(data); + } else if (eventType === CONSTANTS.EVENTS.BID_RESPONSE) { + data = filterBidResponse(data); + } + + // add all ingested events + pwEvents.push({ + eventType: eventType, + args: data + }); + } else { + pwInfo(`Skipping Event ${eventType} ${pwAnalyticsEnabled}`, data); + } + + // once the auction ends, or the event is a bid won send events + if (eventType === CONSTANTS.EVENTS.AUCTION_END || eventType === CONSTANTS.EVENTS.BID_WON) { + flushEvents(); + } +} + +pubwiseAnalytics.storeSessionID = function (userSessID) { + storage.setDataInLocalStorage(localStorageSessName(), userSessID); + pwInfo(`New Session Generated`, userSessID); +}; + +// ensure a session exists, if not make one, always store it +pubwiseAnalytics.ensureSession = function () { + if (sessionExpired() === true || userSessionID() === null || userSessionID() === '') { + let generatedId = utils.generateUUID(); + expireUtmData(); + this.storeSessionID(generatedId); + sessionData.sessionId = generatedId; + } + // eslint-disable-next-line + // console.log('ensured session'); + extendUserSessionTimeout(); +}; + pubwiseAnalytics.adapterEnableAnalytics = pubwiseAnalytics.enableAnalytics; pubwiseAnalytics.enableAnalytics = function (config) { - if (config.options.debug === undefined) { - config.options.debug = utils.debugTurnedOn(); + configOptions = Object.assign(configOptions, config.options); + // take the PBJS debug for our debug setting if no PW debug is defined + if (configOptions.debug === null) { + configOptions.debug = utils.debugTurnedOn(); } - configOptions = config.options; markEnabled(); + sessionData.activationId = utils.generateUUID(); + this.ensureSession(); pubwiseAnalytics.adapterEnableAnalytics(config); }; diff --git a/test/spec/modules/pubwiseAnalyticsAdapter_spec.js b/test/spec/modules/pubwiseAnalyticsAdapter_spec.js index 5e4b2be894e..3be4ea3d69c 100644 --- a/test/spec/modules/pubwiseAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubwiseAnalyticsAdapter_spec.js @@ -1,47 +1,164 @@ +import { expect } from 'chai'; import pubwiseAnalytics from 'modules/pubwiseAnalyticsAdapter.js'; +import {server} from 'test/mocks/xhr.js'; let events = require('src/events'); let adapterManager = require('src/adapterManager').default; let constants = require('src/constants.json'); describe('PubWise Prebid Analytics', function () { - after(function () { + let requests; + let sandbox; + let xhr; + let clock; + let mock = {}; + + mock.DEFAULT_PW_CONFIG = { + provider: 'pubwiseanalytics', + options: { + site: ['b1ccf317-a6fc-428d-ba69-0c9c208aa61c'], + custom: {'c_script_type': 'test-script-type', 'c_host': 'test-host', 'c_slot1': 'test-slot1', 'c_slot2': 'test-slot2', 'c_slot3': 'test-slot3', 'c_slot4': 'test-slot4'} + } + }; + mock.AUCTION_INIT = {auctionId: '53c35d77-bd62-41e7-b920-244140e30c77'}; + mock.AUCTION_INIT_EXTRAS = { + auctionId: '53c35d77-bd62-41e7-b920-244140e30c77', + adUnitCodes: 'not empty', + adUnits: '', + bidderRequests: ['0'], + bidsReceived: '0', + config: {test: 'config'}, + noBids: 'no bids today', + winningBids: 'winning bids', + extraProp: 'extraProp retained' + }; + + beforeEach(function() { + sandbox = sinon.sandbox.create(); + clock = sandbox.useFakeTimers(); + sandbox.stub(events, 'getEvents').returns([]); + + xhr = sandbox.useFakeXMLHttpRequest(); + requests = []; + xhr.onCreate = request => requests.push(request); + }); + + afterEach(function () { + sandbox.restore(); + clock.restore(); pubwiseAnalytics.disableAnalytics(); }); describe('enableAnalytics', function () { beforeEach(function () { - sinon.stub(events, 'getEvents').returns([]); - }); - - afterEach(function () { - events.getEvents.restore(); + requests = []; }); it('should catch all events', function () { - sinon.spy(pubwiseAnalytics, 'track'); + pubwiseAnalytics.enableAnalytics(mock.DEFAULT_PW_CONFIG); - adapterManager.registerAnalyticsAdapter({ - code: 'pubwiseanalytics', - adapter: pubwiseAnalytics - }); + sandbox.spy(pubwiseAnalytics, 'track'); - adapterManager.enableAnalytics({ - provider: 'pubwiseanalytics', - options: { - site: ['test-test-test-test'] - } - }); - - events.emit(constants.EVENTS.AUCTION_INIT, {}); + // sent + events.emit(constants.EVENTS.AUCTION_INIT, mock.AUCTION_INIT); events.emit(constants.EVENTS.BID_REQUESTED, {}); events.emit(constants.EVENTS.BID_RESPONSE, {}); events.emit(constants.EVENTS.BID_WON, {}); + events.emit(constants.EVENTS.AD_RENDER_FAILED, {}); + events.emit(constants.EVENTS.TCF2_ENFORCEMENT, {}); + events.emit(constants.EVENTS.BID_TIMEOUT, {}); + // forces flush events.emit(constants.EVENTS.AUCTION_END, {}); - events.emit(constants.EVENTS.BID_TIMEOUT, {}); + + // eslint-disable-next-line + //console.log(requests); /* testing for 6 calls, including the 2 we're not currently tracking */ - sinon.assert.callCount(pubwiseAnalytics.track, 6); + sandbox.assert.callCount(pubwiseAnalytics.track, 7); + }); + + it('should initialize the auction properly', function () { + pubwiseAnalytics.enableAnalytics(mock.DEFAULT_PW_CONFIG); + + // sent + events.emit(constants.EVENTS.AUCTION_INIT, mock.AUCTION_INIT); + events.emit(constants.EVENTS.BID_REQUESTED, {}); + events.emit(constants.EVENTS.BID_RESPONSE, {}); + events.emit(constants.EVENTS.BID_WON, {}); + // force flush + clock.tick(500); + + /* check for critical values */ + let request = requests[0]; + let data = JSON.parse(request.requestBody); + // eslint-disable-next-line + // console.log(data.metaData); + expect(data.metaData, 'metaData property').to.exist; + expect(data.metaData.pbjs_version, 'pbjs version').to.equal('$prebid.version$') + expect(data.metaData.session_id, 'session id').not.to.be.empty + expect(data.metaData.activation_id, 'activation id').not.to.be.empty + + // check custom metadata slots + expect(data.metaData.c_script_type, 'c_script_type property').to.exist; + expect(data.metaData.c_script_type, 'c_script_type').not.to.be.empty + expect(data.metaData.c_script_type).to.equal('test-script-type'); + + expect(data.metaData.c_host, 'c_host property').to.exist; + expect(data.metaData.c_host, 'c_host').not.to.be.empty + expect(data.metaData.c_host).to.equal('test-host'); + + expect(data.metaData.c_slot1, 'c_slot1 property').to.exist; + expect(data.metaData.c_slot1, 'c_slot1').not.to.be.empty + expect(data.metaData.c_slot1).to.equal('test-slot1'); + + expect(data.metaData.c_slot2, 'c_slot1 property').to.exist; + expect(data.metaData.c_slot2, 'c_slot1').not.to.be.empty + expect(data.metaData.c_slot2).to.equal('test-slot2'); + + expect(data.metaData.c_slot3, 'c_slot1 property').to.exist; + expect(data.metaData.c_slot3, 'c_slot1').not.to.be.empty + expect(data.metaData.c_slot3).to.equal('test-slot3'); + + expect(data.metaData.c_slot4, 'c_slot1 property').to.exist; + expect(data.metaData.c_slot4, 'c_slot1').not.to.be.empty + expect(data.metaData.c_slot4).to.equal('test-slot4'); + + // check for version info too + expect(data.metaData.pw_version, 'pw_version property').to.exist; + expect(data.metaData.pbjs_version, 'pbjs_version property').to.exist; + }); + + it('should remove extra data on init', function () { + pubwiseAnalytics.enableAnalytics(mock.DEFAULT_PW_CONFIG); + + // sent + events.emit(constants.EVENTS.AUCTION_INIT, mock.AUCTION_INIT_EXTRAS); + // force flush + clock.tick(500); + + /* check for critical values */ + let request = requests[0]; + let data = JSON.parse(request.requestBody); + + // check the basics + expect(data.eventList, 'eventList property').to.exist; + expect(data.eventList[0], 'eventList property').to.exist; + expect(data.eventList[0].args, 'eventList property').to.exist; + + // eslint-disable-next-line + // console.log(data.eventList[0].args); + + let eventArgs = data.eventList[0].args; + // the props we want removed should go away + expect(eventArgs.adUnitCodes, 'adUnitCodes property').not.to.exist; + expect(eventArgs.bidderRequests, 'adUnitCodes property').not.to.exist; + expect(eventArgs.bidsReceived, 'adUnitCodes property').not.to.exist; + expect(eventArgs.config, 'adUnitCodes property').not.to.exist; + expect(eventArgs.noBids, 'adUnitCodes property').not.to.exist; + expect(eventArgs.winningBids, 'adUnitCodes property').not.to.exist; + + // the extra prop should still exist + expect(eventArgs.extraProp, 'adUnitCodes property').to.exist; }); }); }); From 565d329c7a54aeab20d421d93571745705ba5ad1 Mon Sep 17 00:00:00 2001 From: Nick Jacob Date: Wed, 9 Sep 2020 06:57:45 -0400 Subject: [PATCH 21/34] update amx bid adapter (#5605) * add support for RTI adapters/userID * add coppa support * add first party data support * more flexibility in sizes * enable reporting by ad unit ID * document ad unit ID * add bid request count data to the request --- modules/amxBidAdapter.js | 74 +++++++++++++++++++- modules/amxBidAdapter.md | 1 + test/spec/modules/amxBidAdapter_spec.js | 91 ++++++++++++++++++++++++- 3 files changed, 163 insertions(+), 3 deletions(-) diff --git a/modules/amxBidAdapter.js b/modules/amxBidAdapter.js index 90fcf878d62..2e9529b633c 100644 --- a/modules/amxBidAdapter.js +++ b/modules/amxBidAdapter.js @@ -1,14 +1,18 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { parseUrl, deepAccess, _each, formatQS, getUniqueIdentifierStr, triggerPixel } from '../src/utils.js'; +import { config } from '../src/config.js'; +import { getStorageManager } from '../src/storageManager.js'; const BIDDER_CODE = 'amx'; +const storage = getStorageManager(737, BIDDER_CODE); const SIMPLE_TLD_TEST = /\.co\.\w{2,4}$/; const DEFAULT_ENDPOINT = 'https://prebid.a-mo.net/a/c'; -const VERSION = 'pba1.0'; +const VERSION = 'pba1.2'; const xmlDTDRxp = /^\s*<\?xml[^\?]+\?>/; const VAST_RXP = /^\s*<\??(?:vast|xml)/i; const TRACKING_ENDPOINT = 'https://1x1.a-mo.net/hbx/'; +const AMUID_KEY = '__amuidpb'; const getLocation = (request) => parseUrl(deepAccess(request, 'refererInfo.canonicalUrl', location.href)) @@ -47,6 +51,22 @@ function getID(loc) { const enc = encodeURIComponent; +function getUIDSafe() { + try { + return storage.getDataFromLocalStorage(AMUID_KEY) + } catch (e) { + return null + } +} + +function setUIDSafe(uid) { + try { + storage.setDataInLocalStorage(AMUID_KEY, uid) + } catch (e) { + // do nothing + } +} + function nestedQs (qsData) { const out = []; Object.keys(qsData || {}).forEach((key) => { @@ -77,9 +97,20 @@ function convertRequest(bid) { const av = isVideoBid || size[1] > 100; const tid = deepAccess(bid, 'params.tagId') + const au = bid.params != null && typeof bid.params.adUnitId === 'string' + ? bid.params.adUnitId : bid.adUnitCode; + + const multiSizes = [ + bid.sizes, + deepAccess(bid, `mediaTypes.${BANNER}.sizes`, []) || [], + deepAccess(bid, `mediaTypes.${VIDEO}.sizes`, []) || [], + ] + const params = { + au, av, vr: isVideoBid, + ms: multiSizes, aw: size[0], ah: size[1], tf: 0, @@ -159,6 +190,16 @@ function resolveSize(bid, request, bidId) { return [bidRequest.aw, bidRequest.ah]; } +function values(source) { + if (Object.values != null) { + return Object.values(source) + } + + return Object.keys(source).map((key) => { + return source[key] + }); +} + export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO], @@ -173,11 +214,19 @@ export const spec = { const loc = getLocation(bidderRequest); const tagId = deepAccess(bidRequests[0], 'params.tagId', null); const testMode = deepAccess(bidRequests[0], 'params.testMode', 0); + const fbid = bidRequests[0] != null ? bidRequests[0] : { + bidderRequestsCount: 0, + bidderWinsCount: 0, + bidRequestsCount: 0 + } const payload = { a: bidderRequest.auctionId, B: 0, b: loc.host, + brc: fbid.bidderRequestsCount || 0, + bwc: fbid.bidderWinsCount || 0, + trc: fbid.bidRequestsCount || 0, tm: testMode, V: '$prebid.version$', i: (testMode && tagId != null) ? tagId : getID(loc), @@ -187,15 +236,32 @@ export const spec = { st: 'prebid', h: screen.height, w: screen.width, - gs: deepAccess(bidderRequest, 'gdprConsent.gdprApplies', '0'), + gs: deepAccess(bidderRequest, 'gdprConsent.gdprApplies', ''), gc: deepAccess(bidderRequest, 'gdprConsent.consentString', ''), u: deepAccess(bidderRequest, 'refererInfo.canonicalUrl', loc.href), do: loc.host, re: deepAccess(bidderRequest, 'refererInfo.referer'), + am: getUIDSafe(), usp: bidderRequest.uspConsent || '1---', smt: 1, d: '', m: createBidMap(bidRequests), + cpp: config.getConfig('coppa') ? 1 : 0, + fpd: config.getConfig('fpd'), + eids: values(bidRequests.reduce((all, bid) => { + // we only want unique ones in here + if (bid == null || bid.userIdAsEids == null) { + return all + } + + _each(bid.userIdAsEids, (value) => { + if (value == null) { + return; + } + all[value.source] = value + }); + return all; + }, {})), }; return { @@ -234,6 +300,10 @@ export const spec = { return []; } + if (response.am && typeof response.am === 'string') { + setUIDSafe(response.am); + } + return flatMap(Object.keys(response.r), (bidID) => { return flatMap(response.r[bidID], (siteBid) => siteBid.b.map((bid) => { diff --git a/modules/amxBidAdapter.md b/modules/amxBidAdapter.md index 4577f5f4f7c..2c38955028f 100644 --- a/modules/amxBidAdapter.md +++ b/modules/amxBidAdapter.md @@ -18,6 +18,7 @@ This module connects web publishers to AMX RTB video and display demand. | --- | -------- | ------- | ----------- | | `testMode` | no | `true` | this will activate test mode / 100% fill with sample ads | | `tagId` | no | `"cHJlYmlkLm9yZw"` | can be used for more specific targeting of inventory. Your account manager will provide this ID if needed | +| `adUnitId` | no | `"sticky_banner"` | optional. To override the bid.adUnitCode provided by prebid. For use in ad-unit level reporting | # Test Parameters diff --git a/test/spec/modules/amxBidAdapter_spec.js b/test/spec/modules/amxBidAdapter_spec.js index 790f3bf2581..4b21244501d 100644 --- a/test/spec/modules/amxBidAdapter_spec.js +++ b/test/spec/modules/amxBidAdapter_spec.js @@ -1,7 +1,9 @@ import * as utils from 'src/utils.js'; +import { createEidsArray } from 'modules/userId/eids.js'; import { expect } from 'chai'; import { spec } from 'modules/amxBidAdapter.js'; import { BANNER, VIDEO } from 'src/mediaTypes.js'; +import { config } from 'src/config.js'; const sampleRequestId = '82c91e127a9b93e'; const sampleDisplayAd = (additionalImpressions) => `${additionalImpressions}`; @@ -14,6 +16,28 @@ const sampleVideoAd = (addlImpression) => ` const embeddedTrackingPixel = `https://1x1.a-mo.net/hbx/g_impression?A=sample&B=20903`; const sampleNurl = 'https://example.exchange/nurl'; +const sampleFPD = { + context: { + keywords: 'sample keywords', + data: { + pageType: 'article' + } + }, + user: { + gender: 'O', + yob: 1982, + } +}; + +const stubConfig = (withStub) => { + const stub = sinon.stub(config, 'getConfig').callsFake( + (arg) => arg === 'fpd' ? sampleFPD : null + ) + + withStub(); + stub.restore(); +}; + const sampleBidderRequest = { gdprConsent: { gdprApplies: true, @@ -165,12 +189,59 @@ describe('AmxBidAdapter', () => { expect(data.tm).to.equal(true); }); - it('handles referer data and GDPR, USP Consent', () => { + it('handles referer data and GDPR, USP Consent, COPPA', () => { const { data } = spec.buildRequests([sampleBidRequestBase], sampleBidderRequest); delete data.m; // don't deal with "m" in this test expect(data.gs).to.equal(sampleBidderRequest.gdprConsent.gdprApplies) expect(data.gc).to.equal(sampleBidderRequest.gdprConsent.consentString) expect(data.usp).to.equal(sampleBidderRequest.uspConsent) + expect(data.cpp).to.equal(0) + }); + + it('will forward bid request count & wins count data', () => { + const bidderRequestsCount = Math.floor(Math.random() * 100) + const bidderWinsCount = Math.floor(Math.random() * 100) + const { data } = spec.buildRequests([{ + ...sampleBidRequestBase, + bidderRequestsCount, + bidderWinsCount + }], sampleBidderRequest); + + expect(data.brc).to.equal(bidderRequestsCount) + expect(data.bwc).to.equal(bidderWinsCount) + expect(data.trc).to.equal(0) + }); + it('will forward first-party data', () => { + stubConfig(() => { + const { data } = spec.buildRequests([sampleBidRequestBase], sampleBidderRequest); + expect(data.fpd).to.deep.equal(sampleFPD) + }); + }); + + it('will collect & forward RTI user IDs', () => { + const randomRTI = `greatRTI${Math.floor(Math.random() * 100)}` + const userId = { + britepoolid: 'sample-britepool', + criteoId: 'sample-criteo', + digitrustid: {data: {id: 'sample-digitrust'}}, + id5id: 'sample-id5', + idl_env: 'sample-liveramp', + lipb: {lipbid: 'sample-liveintent'}, + netId: 'sample-netid', + parrableId: { eid: 'sample-parrable' }, + pubcid: 'sample-pubcid', + [randomRTI]: 'sample-unknown', + tdid: 'sample-ttd', + }; + + const eids = createEidsArray(userId); + const bid = { + ...sampleBidRequestBase, + userIdAsEids: eids + }; + + const { data } = spec.buildRequests([bid, bid], sampleBidderRequest); + expect(data.eids).to.deep.equal(eids) }); it('can build a banner request', () => { @@ -188,6 +259,12 @@ describe('AmxBidAdapter', () => { expect(Object.keys(data.m).length).to.equal(2); expect(data.m[sampleRequestId]).to.deep.equal({ av: true, + au: 'div-gpt-ad-example', + ms: [ + [[320, 50]], + [[300, 250]], + [] + ], aw: 300, ah: 250, tf: 0, @@ -196,6 +273,12 @@ describe('AmxBidAdapter', () => { expect(data.m[sampleRequestId + '_2']).to.deep.equal({ av: true, aw: 300, + au: 'div-gpt-ad-example', + ms: [ + [[320, 50]], + [[300, 250]], + [] + ], i: 'example', ah: 250, tf: 0, @@ -207,6 +290,12 @@ describe('AmxBidAdapter', () => { const { data } = spec.buildRequests([sampleBidRequestVideo], sampleBidderRequest); expect(Object.keys(data.m).length).to.equal(1); expect(data.m[sampleRequestId + '_video']).to.deep.equal({ + au: 'div-gpt-ad-example', + ms: [ + [[300, 150]], + [], + [[360, 250]] + ], av: true, aw: 360, ah: 250, From 85cf495aaede7c7b51dfa07875ff2c49018b4584 Mon Sep 17 00:00:00 2001 From: Pub-X <63354024+Pub-X@users.noreply.github.com> Date: Thu, 10 Sep 2020 03:27:30 +0900 Subject: [PATCH 22/34] Add Pub-X Bid adapter (#5676) * add Pub-X Bid Adapter * add Pub-X Bid Adapter * remove alias --- modules/pubxBidAdapter.js | 47 +++++++++ modules/pubxBidAdapter.md | 32 ++++++ test/spec/modules/pubxBidAdapter_spec.js | 125 +++++++++++++++++++++++ 3 files changed, 204 insertions(+) create mode 100644 modules/pubxBidAdapter.js create mode 100644 modules/pubxBidAdapter.md create mode 100644 test/spec/modules/pubxBidAdapter_spec.js diff --git a/modules/pubxBidAdapter.js b/modules/pubxBidAdapter.js new file mode 100644 index 00000000000..44c95e8e19a --- /dev/null +++ b/modules/pubxBidAdapter.js @@ -0,0 +1,47 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +const BIDDER_CODE = 'pubx'; +const BID_ENDPOINT = 'https://api.primecaster.net/adlogue/api/slot/bid'; +export const spec = { + code: BIDDER_CODE, + isBidRequestValid: function(bid) { + if (!(bid.params.sid)) { + return false; + } else { return true } + }, + buildRequests: function(validBidRequests) { + return validBidRequests.map(bidRequest => { + const bidId = bidRequest.bidId; + const params = bidRequest.params; + const sid = params.sid; + const payload = { + sid: sid + }; + return { + id: bidId, + method: 'GET', + url: BID_ENDPOINT, + data: payload, + } + }); + }, + interpretResponse: function(serverResponse, bidRequest) { + const body = serverResponse.body; + const bidResponses = []; + if (body.cid) { + const bidResponse = { + requestId: bidRequest.id, + cpm: body.cpm, + currency: body.currency, + width: body.width, + height: body.height, + creativeId: body.cid, + netRevenue: true, + ttl: body.TTL, + ad: body.adm + }; + bidResponses.push(bidResponse); + } else {}; + return bidResponses; + } +} +registerBidder(spec); diff --git a/modules/pubxBidAdapter.md b/modules/pubxBidAdapter.md new file mode 100644 index 00000000000..da7d960c831 --- /dev/null +++ b/modules/pubxBidAdapter.md @@ -0,0 +1,32 @@ +# Overview + +Module Name: pubx Bid Adapter + +Maintainer: x@pub-x.io + +# Description + +Module that connects to Pub-X's demand sources +Supported MediaTypes: banner only + +# Test Parameters +```javascript + var adUnits = [ + { + code: 'test', + mediaTypes: { + banner: { + sizes: [300,250] + } + }, + bids: [ + { + bidder: 'pubx', + params: { + sid: 'eDMR' //ID should be provided by Pub-X + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/test/spec/modules/pubxBidAdapter_spec.js b/test/spec/modules/pubxBidAdapter_spec.js new file mode 100644 index 00000000000..d5f1a0f5da3 --- /dev/null +++ b/test/spec/modules/pubxBidAdapter_spec.js @@ -0,0 +1,125 @@ +import {expect} from 'chai'; +import {spec} from 'modules/pubxBidAdapter.js'; +import {newBidder} from 'src/adapters/bidderFactory.js'; + +describe('pubxAdapter', function () { + const adapter = newBidder(spec); + const ENDPOINT = 'https://api.primecaster.net/adlogue/api/slot/bid'; + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + const bid = { + bidder: 'pubx', + params: { + sid: '12345abc' + } + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + const bidRequests = [ + { + id: '26c1ee0038ac11', + params: { + sid: '12345abc' + } + } + ]; + + const data = { + banner: { + sid: '12345abc' + } + }; + + it('sends bid request to ENDPOINT via GET', function () { + const request = spec.buildRequests(bidRequests)[0]; + expect(request.url).to.equal(ENDPOINT); + expect(request.method).to.equal('GET'); + }); + + it('should attach params to the banner request', function () { + const request = spec.buildRequests(bidRequests)[0]; + expect(request.data).to.deep.equal(data.banner); + }); + }); + + describe('interpretResponse', function () { + const serverResponse = { + body: { + TTL: 300, + adm: '
some creative
', + cid: 'TKmB', + cpm: 500, + currency: 'JPY', + height: 250, + width: 300, + } + } + + const bidRequests = [ + { + id: '26c1ee0038ac11', + params: { + sid: '12345abc' + } + } + ]; + + const bidResponses = [ + { + requestId: '26c1ee0038ac11', + cpm: 500, + currency: 'JPY', + width: 300, + height: 250, + creativeId: 'TKmB', + netRevenue: true, + ttl: 300, + ad: '
some creative
' + } + ]; + it('should return empty array when required param is empty', function () { + const serverResponseWithCidEmpty = { + body: { + TTL: 300, + adm: '
some creative
', + cid: '', + cpm: '', + currency: 'JPY', + height: 250, + width: 300, + } + } + const result = spec.interpretResponse(serverResponseWithCidEmpty, bidRequests[0]); + expect(result).to.be.empty; + }); + it('handles banner responses', function () { + const result = spec.interpretResponse(serverResponse, bidRequests[0])[0]; + expect(result.requestId).to.equal(bidResponses[0].requestId); + expect(result.width).to.equal(bidResponses[0].width); + expect(result.height).to.equal(bidResponses[0].height); + expect(result.creativeId).to.equal(bidResponses[0].creativeId); + expect(result.currency).to.equal(bidResponses[0].currency); + expect(result.netRevenue).to.equal(bidResponses[0].netRevenue); + expect(result.ttl).to.equal(bidResponses[0].ttl); + expect(result.ad).to.equal(bidResponses[0].ad); + }); + }); +}); From 9a92a2203f5efad3f61ca063a26a285df9c6672b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aleksandr=20=C5=A0t=C5=A1epelin?= Date: Thu, 10 Sep 2020 00:17:19 +0300 Subject: [PATCH 23/34] New adapter "Cointraffic" added (#5695) * New adapter "Cointraffic" added * removed mobile detection * The sizes property has been updated, added supportedMediaTypes. --- modules/cointrafficBidAdapter.js | 81 ++++++++++ modules/cointrafficBidAdapter.md | 28 ++++ .../modules/cointrafficBidAdapter_spec.js | 145 ++++++++++++++++++ 3 files changed, 254 insertions(+) create mode 100644 modules/cointrafficBidAdapter.js create mode 100644 modules/cointrafficBidAdapter.md create mode 100644 test/spec/modules/cointrafficBidAdapter_spec.js diff --git a/modules/cointrafficBidAdapter.js b/modules/cointrafficBidAdapter.js new file mode 100644 index 00000000000..aa6860d1fc6 --- /dev/null +++ b/modules/cointrafficBidAdapter.js @@ -0,0 +1,81 @@ +import * as utils from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js' + +const BIDDER_CODE = 'cointraffic'; +const ENDPOINT_URL = 'https://appspb.cointraffic.io/pb/tmp'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function (bid) { + return !!(bid.params.placementId); + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param validBidRequests + * @param bidderRequest + * @return Array Info describing the request to the server. + */ + buildRequests: function (validBidRequests, bidderRequest) { + return validBidRequests.map(bidRequest => { + const sizes = utils.parseSizesInput(bidRequest.params.size || bidRequest.sizes); + + const payload = { + placementId: bidRequest.params.placementId, + sizes: sizes, + bidId: bidRequest.bidId, + referer: bidderRequest.refererInfo.referer, + }; + + return { + method: 'POST', + url: ENDPOINT_URL, + data: payload + }; + }); + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @param bidRequest + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse, bidRequest) { + const bidResponses = []; + const response = serverResponse.body; + + if (utils.isEmpty(response)) { + return bidResponses; + } + + const bidResponse = { + requestId: response.requestId, + cpm: response.cpm, + currency: response.currency, + netRevenue: response.netRevenue, + width: response.width, + height: response.height, + creativeId: response.creativeId, + ttl: response.ttl, + ad: response.ad + }; + + bidResponses.push(bidResponse); + + return bidResponses; + } +}; + +registerBidder(spec); diff --git a/modules/cointrafficBidAdapter.md b/modules/cointrafficBidAdapter.md new file mode 100644 index 00000000000..ad608a1319e --- /dev/null +++ b/modules/cointrafficBidAdapter.md @@ -0,0 +1,28 @@ +# Overview + +``` +Module Name: Cointraffic Bidder Adapter +Module Type: Cointraffic Adapter +Maintainer: tech@cointraffic.io +``` + +# Description +The Cointraffic client module makes it easy to implement Cointraffic directly into your website. To get started, simply replace the ``placementId`` with your assigned tracker key. This is dependent on the size required by your account dashboard. For additional information on this module, please contact us at ``support@cointraffic.io``. + +# Test Parameters +``` + var adUnits = [{ + code: 'test-ad-div', + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, + bids: [{ + bidder: 'cointraffic', + params: { + placementId: 'testPlacementId' + } + }] + }]; +``` diff --git a/test/spec/modules/cointrafficBidAdapter_spec.js b/test/spec/modules/cointrafficBidAdapter_spec.js new file mode 100644 index 00000000000..6d948e36cb9 --- /dev/null +++ b/test/spec/modules/cointrafficBidAdapter_spec.js @@ -0,0 +1,145 @@ +import { expect } from 'chai'; +import { spec } from 'modules/cointrafficBidAdapter.js'; + +const ENDPOINT_URL = 'https://appspb.cointraffic.io/pb/tmp'; + +describe('cointrafficBidAdapter', function () { + describe('isBidRequestValid', function () { + let bid = { + bidder: 'cointraffic', + params: { + placementId: 'testPlacementId' + }, + adUnitCode: 'adunit-code', + sizes: [ + [300, 250] + ], + bidId: 'bidId12345', + bidderRequestId: 'bidderRequestId12345', + auctionId: 'auctionId12345' + }; + + it('should return true where required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + }); + + describe('buildRequests', function () { + let bidRequests = [ + { + bidder: 'cointraffic', + params: { + placementId: 'testPlacementId' + }, + adUnitCode: 'adunit-code', + sizes: [ + [300, 250] + ], + bidId: 'bidId12345', + bidderRequestId: 'bidderRequestId12345', + auctionId: 'auctionId12345', + }, + { + bidder: 'cointraffic', + params: { + placementId: 'testPlacementId' + }, + adUnitCode: 'adunit-code2', + sizes: [ + [300, 250] + ], + bidId: 'bidId67890"', + bidderRequestId: 'bidderRequestId67890', + auctionId: 'auctionId12345', + } + ]; + + let bidderRequests = { + refererInfo: { + numIframes: 0, + reachedTop: true, + referer: 'https://example.com', + stack: [ + 'https://example.com' + ] + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequests); + + it('sends bid request to our endpoint via POST', function () { + expect(request[0].method).to.equal('POST'); + expect(request[1].method).to.equal('POST'); + }); + it('attaches source and version to endpoint URL as query params', function () { + expect(request[0].url).to.equal(ENDPOINT_URL); + expect(request[1].url).to.equal(ENDPOINT_URL); + }); + }); + + describe('interpretResponse', function () { + let bidRequest = [ + { + method: 'POST', + url: ENDPOINT_URL, + data: { + placementId: 'testPlacementId', + device: 'desktop', + sizes: ['300x250'], + bidId: 'bidId12345', + referer: 'www.example.com' + } + } + ]; + + it('should get the correct bid response', function () { + let serverResponse = { + body: { + requestId: 'bidId12345', + cpm: 3.9, + currency: 'EUR', + netRevenue: true, + width: 300, + height: 250, + creativeId: 'creativeId12345', + ttl: 90, + ad: '

I am an ad

', + } + }; + + let expectedResponse = [{ + requestId: 'bidId12345', + cpm: 3.9, + currency: 'EUR', + netRevenue: true, + width: 300, + height: 250, + creativeId: 'creativeId12345', + ttl: 90, + ad: '

I am an ad

' + }]; + let result = spec.interpretResponse(serverResponse, bidRequest[0]); + expect(Object.keys(result)).to.deep.equal(Object.keys(expectedResponse)); + }); + + it('should get empty bid response if server response body is empty', function () { + let serverResponse = { + body: {} + }; + + let expectedResponse = []; + + let result = spec.interpretResponse(serverResponse, bidRequest[0]); + expect(Object.keys(result)).to.deep.equal(Object.keys(expectedResponse)); + }); + + it('should get empty bid response if no server response', function () { + let serverResponse = {}; + + let expectedResponse = []; + + let result = spec.interpretResponse(serverResponse, bidRequest[0]); + expect(Object.keys(result)).to.deep.equal(Object.keys(expectedResponse)); + }); + }); +}); From a2da917f028110f768b23d8f5652162f12399ada Mon Sep 17 00:00:00 2001 From: bjorn-lw <32431346+bjorn-lw@users.noreply.github.com> Date: Thu, 10 Sep 2020 02:28:27 +0200 Subject: [PATCH 24/34] Send GDPR data in analytics request (#5653) * Livewrapped bid and analytics adapter * Fixed some tests for browser compatibility * Fixed some tests for browser compatibility * Changed analytics adapter code name * Fix double quote in debug message * modified how gdpr is being passed * Added support for Publisher Common ID Module * Corrections for ttr in analytics * ANalytics updates * Auction start time stamp changed * Detect recovered ad blocked requests Make it possible to pass dynamic parameters to adapter * Collect info on ad units receiving any valid bid * Support for ID5 Pass metadata from adapter * Typo in test + eids on wrong level * Fix for Prebid 3.0 * Fix get referer * http -> https in tests * Native support * Read sizes from mediatype.banner * Revert accidental commit * Support native data collection + minor refactorings * Set analytics endpoint * Support for app parameters * Fix issue where adunits with bids were not counted on reload * Send debug info from adapter to external debugger * SChain support * Send GDPR data in analytics request --- modules/livewrappedAnalyticsAdapter.js | 27 ++++++++-- .../livewrappedAnalyticsAdapter_spec.js | 52 +++++++++++++++++-- 2 files changed, 72 insertions(+), 7 deletions(-) diff --git a/modules/livewrappedAnalyticsAdapter.js b/modules/livewrappedAnalyticsAdapter.js index b331448161e..4b1c162c67c 100644 --- a/modules/livewrappedAnalyticsAdapter.js +++ b/modules/livewrappedAnalyticsAdapter.js @@ -34,6 +34,9 @@ let livewrappedAnalyticsAdapter = Object.assign(adapter({EMPTYURL, ANALYTICSTYPE cache.auctions[args.auctionId].timeStamp = args.start; args.bids.forEach(function(bidRequest) { + cache.auctions[args.auctionId].gdprApplies = args.gdprConsent ? args.gdprConsent.gdprApplies : undefined; + cache.auctions[args.auctionId].gdprConsent = args.gdprConsent ? args.gdprConsent.consentString : undefined; + cache.auctions[args.auctionId].bids[bidRequest.bidId] = { bidder: bidRequest.bidder, adUnit: bidRequest.adUnitCode, @@ -116,9 +119,11 @@ livewrappedAnalyticsAdapter.enableAnalytics = function (config) { }; livewrappedAnalyticsAdapter.sendEvents = function() { + var sentRequests = getSentRequests(); var events = { publisherId: initOptions.publisherId, - requests: getSentRequests(), + gdpr: sentRequests.gdpr, + requests: sentRequests.sentRequests, responses: getResponses(), wins: getWins(), timeouts: getTimeouts(), @@ -144,10 +149,23 @@ function getAdblockerRecovered() { function getSentRequests() { var sentRequests = []; + var gdpr = []; Object.keys(cache.auctions).forEach(auctionId => { + let auction = cache.auctions[auctionId]; + var gdprPos = 0; + for (gdprPos = 0; gdprPos < gdpr.length; gdprPos++) { + if (gdpr[gdprPos].gdprApplies == auction.gdprApplies && + gdpr[gdprPos].gdprConsent == auction.gdprConsent) { + break; + } + } + + if (gdprPos == gdpr.length) { + gdpr[gdprPos] = {gdprApplies: auction.gdprApplies, gdprConsent: auction.gdprConsent}; + } + 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; @@ -155,13 +173,14 @@ function getSentRequests() { sentRequests.push({ timeStamp: auction.timeStamp, adUnit: bid.adUnit, - bidder: bid.bidder + bidder: bid.bidder, + gdpr: gdprPos }); } }); }); - return sentRequests; + return {gdpr: gdpr, sentRequests: sentRequests}; } function getResponses() { diff --git a/test/spec/modules/livewrappedAnalyticsAdapter_spec.js b/test/spec/modules/livewrappedAnalyticsAdapter_spec.js index 4e05d1a31ff..c723f589fa0 100644 --- a/test/spec/modules/livewrappedAnalyticsAdapter_spec.js +++ b/test/spec/modules/livewrappedAnalyticsAdapter_spec.js @@ -120,6 +120,7 @@ const MOCK = { const ANALYTICS_MESSAGE = { publisherId: 'CC411485-42BC-4F92-8389-42C503EE38D7', + gdpr: [{}], bidAdUnits: [ { adUnit: 'panorama_d_1', @@ -134,17 +135,20 @@ const ANALYTICS_MESSAGE = { { adUnit: 'panorama_d_1', bidder: 'livewrapped', - timeStamp: 1519149562216 + timeStamp: 1519149562216, + gdpr: 0 }, { adUnit: 'box_d_1', bidder: 'livewrapped', - timeStamp: 1519149562216 + timeStamp: 1519149562216, + gdpr: 0 }, { adUnit: 'box_d_2', bidder: 'livewrapped', - timeStamp: 1519149562216 + timeStamp: 1519149562216, + gdpr: 0 } ], responses: [ @@ -321,6 +325,48 @@ describe('Livewrapped analytics adapter', function () { expect(message.rcv).to.equal(true); }); + + it('should forward GDPR data', function () { + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, { + 'bidder': 'livewrapped', + 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', + 'bidderRequestId': '1be65d7958826a', + 'bids': [ + { + 'bidder': 'livewrapped', + 'adUnitCode': 'panorama_d_1', + 'bidId': '2ecff0db240757', + }, + { + 'bidder': 'livewrapped', + 'adUnitCode': 'box_d_1', + 'bidId': '3ecff0db240757', + } + ], + 'start': 1519149562216, + 'gdprConsent': { + 'gdprApplies': true, + 'consentString': 'consentstring' + } + }, + ); + events.emit(BID_TIMEOUT, MOCK.BID_TIMEOUT); + events.emit(AUCTION_END, MOCK.AUCTION_END); + + clock.tick(BID_WON_TIMEOUT + 1000); + + expect(server.requests.length).to.equal(1); + let request = server.requests[0]; + let message = JSON.parse(request.requestBody); + + expect(message.gdpr.length).to.equal(1); + expect(message.gdpr[0].gdprApplies).to.equal(true); + expect(message.gdpr[0].gdprConsent).to.equal('consentstring'); + expect(message.requests.length).to.equal(2); + expect(message.requests[0].gdpr).to.equal(0); + expect(message.requests[1].gdpr).to.equal(0); + }); }); describe('when given other endpoint', function () { From 8f249dc099db5d9eb7faaf16614b05244f4ea20d Mon Sep 17 00:00:00 2001 From: Neelanjan Sen <14229985+Fawke@users.noreply.github.com> Date: Thu, 10 Sep 2020 14:31:28 +0530 Subject: [PATCH 25/34] GDPR Enforcement - Bugfix (#5686) * consolicate getGVLID function into a single function * pass correct arguments to gvlid getter functions * have one master getGvlid getter function to rule other gvlId getter functions * restore to file state on master branch * remove unnecessary example * remove unnecessary reference from internal object * works on comments and change getgvlidForAnalyticds Adapter to a one liner --- modules/gdprEnforcement.js | 93 ++++++++++++++--------- test/spec/modules/gdprEnforcement_spec.js | 57 +++++++++++++- 2 files changed, 111 insertions(+), 39 deletions(-) diff --git a/modules/gdprEnforcement.js b/modules/gdprEnforcement.js index 97eaedd92be..adbccd8666d 100644 --- a/modules/gdprEnforcement.js +++ b/modules/gdprEnforcement.js @@ -47,58 +47,75 @@ const analyticsBlocked = []; let addedDeviceAccessHook = false; +// Helps in stubbing these functions in unit tests. +export const internal = { + getGvlidForBidAdapter, + getGvlidForUserIdModule, + getGvlidForAnalyticsAdapter +}; + /** - * Returns gvlId for Bid Adapters. If a bidder does not have an associated gvlId, it returns 'undefined'. - * @param {string=} bidderCode - The 'code' property on the Bidder spec. - * @retuns {number} gvlId + * Returns GVL ID for a Bid adapter / an USERID submodule / an Analytics adapter. + * If modules of different types have the same moduleCode: For example, 'appnexus' is the code for both Bid adapter and Analytics adapter, + * then, we assume that their GVL IDs are same. This function first checks if GVL ID is defined for a Bid adapter, if not found, tries to find User ID + * submodule's GVL ID, if not found, tries to find Analytics adapter's GVL ID. In this process, as soon as it finds a GVL ID, it returns it + * without going to the next check. + * @param {{string|Object}} - module + * @return {number} - GVL ID */ -function getGvlid(bidderCode) { - let gvlid; +export function getGvlid(module) { + let gvlid = null; + if (module) { + // Check user defined GVL Mapping in pbjs.setConfig() + const gvlMapping = config.getConfig('gvlMapping'); + + // For USER ID Module, we pass the submodule object itself as the "module" parameter, this check is required to grab the module code + const moduleCode = typeof module === 'string' ? module : module.name; + + // Return GVL ID from user defined gvlMapping + if (gvlMapping && gvlMapping[moduleCode]) { + gvlid = gvlMapping[moduleCode]; + return gvlid; + } + + gvlid = internal.getGvlidForBidAdapter(moduleCode) || internal.getGvlidForUserIdModule(module) || internal.getGvlidForAnalyticsAdapter(moduleCode); + } + return gvlid; +} + +/** + * Returns GVL ID for a bid adapter. If the adapter does not have an associated GVL ID, it returns 'null'. + * @param {string=} bidderCode - The 'code' property of the Bidder spec. + * @return {number} GVL ID + */ +function getGvlidForBidAdapter(bidderCode) { + let gvlid = null; bidderCode = bidderCode || config.getCurrentBidder(); if (bidderCode) { - const gvlMapping = config.getConfig('gvlMapping'); - if (gvlMapping && gvlMapping[bidderCode]) { - gvlid = gvlMapping[bidderCode]; - } else { - const bidder = adapterManager.getBidAdapter(bidderCode); - if (bidder && bidder.getSpec) { - gvlid = bidder.getSpec().gvlid; - } + const bidder = adapterManager.getBidAdapter(bidderCode); + if (bidder && bidder.getSpec) { + gvlid = bidder.getSpec().gvlid; } } return gvlid; } /** - * Returns gvlId for userId module. If a userId modules does not have an associated gvlId, it returns 'undefined'. + * Returns GVL ID for an userId submodule. If an userId submodules does not have an associated GVL ID, it returns 'null'. * @param {Object} userIdModule - * @retuns {number} gvlId + * @return {number} GVL ID */ function getGvlidForUserIdModule(userIdModule) { - let gvlId; - const gvlMapping = config.getConfig('gvlMapping'); - if (gvlMapping && gvlMapping[userIdModule.name]) { - gvlId = gvlMapping[userIdModule.name]; - } else { - gvlId = userIdModule.gvlid; - } - return gvlId; + return (typeof userIdModule === 'object' ? userIdModule.gvlid : null); } /** - * Returns gvlId for analytics adapters. If a analytics adapter does not have an associated gvlId, it returns 'undefined'. + * Returns GVL ID for an analytics adapter. If an analytics adapter does not have an associated GVL ID, it returns 'null'. * @param {string} code - 'provider' property on the analytics adapter config - * @returns {number} gvlId + * @return {number} GVL ID */ function getGvlidForAnalyticsAdapter(code) { - let gvlId; - const gvlMapping = config.getConfig('gvlMapping'); - if (gvlMapping && gvlMapping[code]) { - gvlId = gvlMapping[code]; - } else { - gvlId = adapterManager.getAnalyticsAdapter(code).gvlid; - } - return gvlId; + return adapterManager.getAnalyticsAdapter(code) && (adapterManager.getAnalyticsAdapter(code).gvlid || null); } /** @@ -165,7 +182,7 @@ export function deviceAccessHook(fn, gvlid, moduleName, result) { if (curBidder && (curBidder != moduleName) && adapterManager.aliasRegistry[curBidder] === moduleName) { gvlid = getGvlid(curBidder); } else { - gvlid = getGvlid(moduleName); + gvlid = getGvlid(moduleName) || gvlid; } const curModule = moduleName || curBidder; let isAllowed = validateRules(purpose1Rule, consentData, curModule, gvlid); @@ -199,8 +216,8 @@ export function userSyncHook(fn, ...args) { const consentData = gdprDataHandler.getConsentData(); if (consentData && consentData.gdprApplies) { if (consentData.apiVersion === 2) { - const gvlid = getGvlid(); const curBidder = config.getCurrentBidder(); + const gvlid = getGvlid(curBidder); let isAllowed = validateRules(purpose1Rule, consentData, curBidder, gvlid); if (isAllowed) { fn.call(this, ...args); @@ -227,7 +244,7 @@ export function userIdHook(fn, submodules, consentData) { if (consentData && consentData.gdprApplies) { if (consentData.apiVersion === 2) { let userIdModules = submodules.map((submodule) => { - const gvlid = getGvlidForUserIdModule(submodule.submodule); + const gvlid = getGvlid(submodule.submodule); const moduleName = submodule.submodule.name; let isAllowed = validateRules(purpose1Rule, consentData, moduleName, gvlid); if (isAllowed) { @@ -296,7 +313,7 @@ export function enableAnalyticsHook(fn, config) { } config = config.filter(conf => { const analyticsAdapterCode = conf.provider; - const gvlid = getGvlidForAnalyticsAdapter(analyticsAdapterCode); + const gvlid = getGvlid(analyticsAdapterCode); const isAllowed = !!validateRules(purpose7Rule, consentData, analyticsAdapterCode, gvlid); if (!isAllowed) { analyticsBlocked.push(analyticsAdapterCode); @@ -347,7 +364,7 @@ const hasPurpose7 = (rule) => { return rule.purpose === TCF2.purpose7.name } export function setEnforcementConfig(config) { const rules = utils.deepAccess(config, 'gdpr.rules'); if (!rules) { - utils.logWarn('TCF2: enforcing P1 and P2'); + utils.logWarn('TCF2: enforcing P1 and P2 by default'); enforcementRules = DEFAULT_RULES; } else { enforcementRules = rules; diff --git a/test/spec/modules/gdprEnforcement_spec.js b/test/spec/modules/gdprEnforcement_spec.js index d5c8d7bfb88..82cb70f42be 100644 --- a/test/spec/modules/gdprEnforcement_spec.js +++ b/test/spec/modules/gdprEnforcement_spec.js @@ -8,7 +8,9 @@ import { enforcementRules, purpose1Rule, purpose2Rule, - enableAnalyticsHook + enableAnalyticsHook, + getGvlid, + internal } from 'modules/gdprEnforcement.js'; import { config } from 'src/config.js'; import adapterManager, { gdprDataHandler } from 'src/adapterManager.js'; @@ -1067,4 +1069,57 @@ describe('gdpr enforcement', function () { sinon.assert.calledWith(events.emit.getCall(1), 'tcf2Enforcement', sinon.match.object); }) }); + + describe('getGvlid', function() { + let sandbox; + let getGvlidForBidAdapterStub; + let getGvlidForUserIdModuleStub; + let getGvlidForAnalyticsAdapterStub; + beforeEach(function() { + sandbox = sinon.createSandbox(); + getGvlidForBidAdapterStub = sandbox.stub(internal, 'getGvlidForBidAdapter'); + getGvlidForUserIdModuleStub = sandbox.stub(internal, 'getGvlidForUserIdModule'); + getGvlidForAnalyticsAdapterStub = sandbox.stub(internal, 'getGvlidForAnalyticsAdapter'); + }); + afterEach(function() { + sandbox.restore(); + config.resetConfig(); + }); + + it('should return "null" if called without passing any argument', function() { + const gvlid = getGvlid(); + expect(gvlid).to.equal(null); + }); + + it('should return "null" if GVL ID is not defined for any of these modules: Bid adapter, UserId submodule and Analytics adapter', function() { + getGvlidForBidAdapterStub.withArgs('moduleA').returns(null); + getGvlidForUserIdModuleStub.withArgs('moduleA').returns(null); + getGvlidForAnalyticsAdapterStub.withArgs('moduleA').returns(null); + + const gvlid = getGvlid('moduleA'); + expect(gvlid).to.equal(null); + }); + + it('should return the GVL ID from gvlMapping if it is defined in setConfig', function() { + config.setConfig({ + gvlMapping: { + moduleA: 1 + } + }); + + // Actual GVL ID for moduleA is 2, as defined on its the bidAdapter.js file. + getGvlidForBidAdapterStub.withArgs('moduleA').returns(2); + + const gvlid = getGvlid('moduleA'); + expect(gvlid).to.equal(1); + }); + + it('should return the GVL ID by calling getGvlidForBidAdapter -> getGvlidForUserIdModule -> getGvlidForAnalyticsAdapter in sequence', function() { + getGvlidForBidAdapterStub.withArgs('moduleA').returns(null); + getGvlidForUserIdModuleStub.withArgs('moduleA').returns(null); + getGvlidForAnalyticsAdapterStub.withArgs('moduleA').returns(7); + + expect(getGvlid('moduleA')).to.equal(7); + }); + }); }); From 2acca6f2e1b7a1fc4f6e977936f6a06686cd23b1 Mon Sep 17 00:00:00 2001 From: Nick Date: Thu, 10 Sep 2020 07:38:48 -0400 Subject: [PATCH 26/34] changes SameSite from None to Lax for tests on Chrome 85.0.4183 which added rejection of insecure SameSite=None cookies (#5719) --- test/spec/modules/fintezaAnalyticsAdapter_spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/spec/modules/fintezaAnalyticsAdapter_spec.js b/test/spec/modules/fintezaAnalyticsAdapter_spec.js index 29136c85241..4c76f79f518 100644 --- a/test/spec/modules/fintezaAnalyticsAdapter_spec.js +++ b/test/spec/modules/fintezaAnalyticsAdapter_spec.js @@ -12,7 +12,7 @@ function setCookie(name, value, expires) { document.cookie = name + '=' + value + '; path=/' + (expires ? ('; expires=' + expires.toUTCString()) : '') + - '; SameSite=None'; + '; SameSite=Lax'; } describe('finteza analytics adapter', function () { From c01cab138b66f937ded5a8cb191c902a025ef661 Mon Sep 17 00:00:00 2001 From: Robert Ray Martinez III Date: Thu, 10 Sep 2020 06:41:14 -0700 Subject: [PATCH 27/34] implement issue #5687 (#5716) --- modules/prebidServerBidAdapter/index.js | 3 +++ test/spec/modules/prebidServerBidAdapter_spec.js | 16 ++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index a7a3199675d..0ff967f1da9 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -504,6 +504,9 @@ const OPEN_RTB_PROTOCOL = { // Don't push oustream w/o renderer to request object. utils.logError('Outstream bid without renderer cannot be sent to Prebid Server.'); } else { + if (videoParams.context === 'instream' && !videoParams.hasOwnProperty('placement')) { + videoParams.placement = 1; + } mediaTypes['video'] = videoParams; } } diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index a300a10d31b..d29657e8e53 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -509,6 +509,22 @@ describe('S2S Adapter', function () { expect(requestBid.imp[0].video).to.not.exist; }); + it('should default video placement if not defined and instream', function () { + let ortb2Config = utils.deepClone(CONFIG); + ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; + + config.setConfig({ s2sConfig: ortb2Config }); + + let videoBid = utils.deepClone(VIDEO_REQUEST); + videoBid.ad_units[0].mediaTypes.video.context = 'instream'; + adapter.callBids(videoBid, BID_REQUESTS, addBidResponse, done, ajax); + + const requestBid = JSON.parse(server.requests[0].requestBody); + expect(requestBid.imp[0].banner).to.not.exist; + expect(requestBid.imp[0].video).to.exist; + expect(requestBid.imp[0].video.placement).to.equal(1); + }); + it('exists and is a function', function () { expect(adapter.callBids).to.exist.and.to.be.a('function'); }); From 466b49e85a1e173de255ef31c61a73f0cc4d8c57 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Thu, 10 Sep 2020 10:17:45 -0400 Subject: [PATCH 28/34] allow publisher to define backup renderer (#5638) * Update Renderer.js * Update auction.js * Update renderer_spec.js * Update auctionmanager_spec.js * Update prebidServerBidAdapter_spec.js --- src/Renderer.js | 6 ++--- src/auction.js | 4 ++-- test/spec/auctionmanager_spec.js | 23 +++++++++++++++++++ .../modules/prebidServerBidAdapter_spec.js | 2 +- test/spec/renderer_spec.js | 23 +++++++++++++++++++ 5 files changed, 52 insertions(+), 6 deletions(-) diff --git a/src/Renderer.js b/src/Renderer.js index 85bcbb383e8..f073d97d052 100644 --- a/src/Renderer.js +++ b/src/Renderer.js @@ -39,7 +39,7 @@ export function Renderer(options) { // use a function, not an arrow, in order to be able to pass "arguments" through this.render = function () { - if (!isRendererDefinedOnAdUnit(adUnitCode)) { + if (!isRendererPreferredFromAdUnit(adUnitCode)) { // we expect to load a renderer url once only so cache the request to load script loadExternalScript(url, moduleCode, this.callback); } else { @@ -110,10 +110,10 @@ export function executeRenderer(renderer, bid) { renderer.render(bid); } -function isRendererDefinedOnAdUnit(adUnitCode) { +function isRendererPreferredFromAdUnit(adUnitCode) { const adUnits = $$PREBID_GLOBAL$$.adUnits; const adUnit = find(adUnits, adUnit => { return adUnit.code === adUnitCode; }); - return !!(adUnit && adUnit.renderer && adUnit.renderer.url && adUnit.renderer.render); + return !!(adUnit && adUnit.renderer && adUnit.renderer.url && adUnit.renderer.render && !(utils.isBoolean(adUnit.renderer.backupOnly) && adUnit.renderer.backupOnly)); } diff --git a/src/auction.js b/src/auction.js index b82b4752479..5858c3edf78 100644 --- a/src/auction.js +++ b/src/auction.js @@ -57,7 +57,7 @@ * @property {function(): void} callBids - sends requests to all adapters for bids */ -import {flatten, timestamp, adUnitsFilter, deepAccess, getBidRequest, getValue, parseUrl} from './utils.js'; +import {flatten, timestamp, adUnitsFilter, deepAccess, getBidRequest, getValue, parseUrl, isBoolean} from './utils.js'; import { getPriceBucketString } from './cpmBucketManager.js'; import { getNativeTargeting } from './native.js'; import { getCacheUrl, store } from './videoCache.js'; @@ -512,7 +512,7 @@ function getPreparedBidForAuction({adUnitCode, bid, bidderRequest, auctionId}) { const bidReq = bidderRequest.bids && find(bidderRequest.bids, bid => bid.adUnitCode == adUnitCode); const adUnitRenderer = bidReq && bidReq.renderer; - if (adUnitRenderer && adUnitRenderer.url) { + if (adUnitRenderer && adUnitRenderer.url && !(adUnitRenderer.backupOnly && isBoolean(adUnitRenderer.backupOnly) && bid.renderer)) { bidObject.renderer = Renderer.install({ url: adUnitRenderer.url }); bidObject.renderer.setRender(adUnitRenderer.render); } diff --git a/test/spec/auctionmanager_spec.js b/test/spec/auctionmanager_spec.js index 35a29727614..e35b1406fbf 100644 --- a/test/spec/auctionmanager_spec.js +++ b/test/spec/auctionmanager_spec.js @@ -740,6 +740,29 @@ describe('auctionmanager.js', function () { assert.equal(addedBid.renderer.url, 'renderer.js'); }); + it('installs publisher-defined backup renderers on bids', function () { + let renderer = { + url: 'renderer.js', + backupOnly: true, + render: (bid) => bid + }; + let bidRequests = [Object.assign({}, TEST_BID_REQS[0])]; + bidRequests[0].bids[0] = Object.assign({ renderer }, bidRequests[0].bids[0]); + makeRequestsStub.returns(bidRequests); + + let bids1 = Object.assign({}, + bids[0], + { + bidderCode: BIDDER_CODE, + mediaType: 'video-outstream', + } + ); + spec.interpretResponse.returns(bids1); + auction.callBids(); + const addedBid = auction.getBidsReceived().pop(); + assert.equal(addedBid.renderer.url, 'renderer.js'); + }); + it('bid for a regular unit and a video unit', function() { let renderer = { url: 'renderer.js', diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index d29657e8e53..d6f755914e5 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -497,7 +497,7 @@ describe('S2S Adapter', function () { resetSyncedStatus(); }); - it('should not add outstrean without renderer', function () { + it('should not add outstream without renderer', function () { let ortb2Config = utils.deepClone(CONFIG); ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; diff --git a/test/spec/renderer_spec.js b/test/spec/renderer_spec.js index 77d806e4dbc..9bf551f35e8 100644 --- a/test/spec/renderer_spec.js +++ b/test/spec/renderer_spec.js @@ -132,6 +132,29 @@ describe('Renderer', function () { expect(utilsSpy.callCount).to.equal(1); }); + it('should load renderer adunit renderer when backupOnly', function() { + $$PREBID_GLOBAL$$.adUnits = [{ + code: 'video1', + renderer: { + url: 'http://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js', + backupOnly: true, + render: sinon.spy() + } + }] + + let testRenderer = Renderer.install({ + url: 'https://httpbin.org/post', + config: { test: 'config1' }, + id: 1, + adUnitCode: 'video1' + + }); + testRenderer.setRender(() => {}) + + testRenderer.render() + expect(loadExternalScript.called).to.be.true; + }); + it('should call loadExternalScript() for script not defined on adUnit, only when .render() is called', function() { $$PREBID_GLOBAL$$.adUnits = [{ code: 'video1', From 22bb7581484fef0a06c13cb283b624e9abaf7f34 Mon Sep 17 00:00:00 2001 From: Kanchika - Automatad Date: Fri, 11 Sep 2020 00:30:21 +0530 Subject: [PATCH 29/34] Automatad Bid Adapter: Support multiple bids in response (#5699) * added automatad bid adapter * added automatad bid adapter readme * added automatad bidder adapter unit test * updated maintainer email id for automatad adapter * refactored automatadBidAdapter js * refactored automatadBidAdapter unit test * refactored automatadBidAdapter unit test * added usersync code to automatad bid adapter * Added unit test for onBidWon in automatadBidAdapter_spec * removed trailing spaces * removed trailing space * changes for getUserSync function * lint error fixes * updated usersync url * additional test for onBidWon function added * added ajax stub in test * updated winurl params * lint fixes * added adunitCode in bid request * added test for adunit code * add placement in impression object * added code to interpret multiple bid response in seatbid --- modules/automatadBidAdapter.js | 30 +++++------ test/spec/modules/automatadBidAdapter_spec.js | 50 +++++++++++++++++++ 2 files changed, 66 insertions(+), 14 deletions(-) diff --git a/modules/automatadBidAdapter.js b/modules/automatadBidAdapter.js index e1a69a37513..6b66044f5e5 100644 --- a/modules/automatadBidAdapter.js +++ b/modules/automatadBidAdapter.js @@ -71,20 +71,22 @@ export const spec = { const bidResponses = [] const response = (serverResponse || {}).body - if (response && response.seatbid && response.seatbid.length === 1 && response.seatbid[0].bid && response.seatbid[0].bid.length) { - response.seatbid[0].bid.forEach(bid => { - bidResponses.push({ - requestId: bid.impid, - cpm: bid.price, - ad: bid.adm, - adDomain: bid.adomain[0], - currency: DEFAULT_CURRENCY, - ttl: DEFAULT_BID_TTL, - creativeId: bid.crid, - width: bid.w, - height: bid.h, - netRevenue: DEFAULT_NET_REVENUE, - nurl: bid.nurl, + if (response && response.seatbid && response.seatbid[0].bid && response.seatbid[0].bid.length) { + response.seatbid.forEach(bidObj => { + bidObj.bid.forEach(bid => { + bidResponses.push({ + requestId: bid.impid, + cpm: bid.price, + ad: bid.adm, + adDomain: bid.adomain[0], + currency: DEFAULT_CURRENCY, + ttl: DEFAULT_BID_TTL, + creativeId: bid.crid, + width: bid.w, + height: bid.h, + netRevenue: DEFAULT_NET_REVENUE, + nurl: bid.nurl, + }) }) }) } else { diff --git a/test/spec/modules/automatadBidAdapter_spec.js b/test/spec/modules/automatadBidAdapter_spec.js index e0341a1d255..fca1a464ff2 100644 --- a/test/spec/modules/automatadBidAdapter_spec.js +++ b/test/spec/modules/automatadBidAdapter_spec.js @@ -113,6 +113,56 @@ describe('automatadBidAdapter', function () { expect(result).to.be.an('array').that.is.not.empty }) + it('should interpret multiple bids in seatbid', function () { + let multipleBidResponse = [{ + 'body': { + 'id': 'abc-321', + 'seatbid': [ + { + 'bid': [ + { + 'adm': '', + 'adomain': [ + 'someAdDomain' + ], + 'crid': 123, + 'h': 600, + 'id': 'bid1', + 'impid': 'imp1', + 'nurl': 'https://example/win', + 'price': 0.5, + 'w': 300 + } + ] + }, { + 'bid': [ + { + 'adm': '', + 'adomain': [ + 'someAdDomain' + ], + 'crid': 321, + 'h': 600, + 'id': 'bid1', + 'impid': 'imp2', + 'nurl': 'https://example/win', + 'price': 0.5, + 'w': 300 + } + ] + } + ] + } + }] + let result = spec.interpretResponse(multipleBidResponse[0]).map(bid => { + const {requestId} = bid; + return [ requestId ]; + }); + + assert.equal(result.length, 2); + assert.deepEqual(result, [[ 'imp1' ], [ 'imp2' ]]); + }) + it('handles empty bid response', function () { let response = { body: '' From 277fb9b98ade48704211876a56074f9482f3a106 Mon Sep 17 00:00:00 2001 From: Carlos Barreiro Mata Date: Fri, 11 Sep 2020 00:29:45 +0200 Subject: [PATCH 30/34] Fix: check mandatory video params (#5470) * Fix: check mandatory video params * Simplifying mediaType video existence check --- modules/seedtagBidAdapter.js | 12 ++--- test/spec/modules/seedtagBidAdapter_spec.js | 59 ++++++++++++++------- 2 files changed, 47 insertions(+), 24 deletions(-) diff --git a/modules/seedtagBidAdapter.js b/modules/seedtagBidAdapter.js index 018339fabe4..e1832e50020 100644 --- a/modules/seedtagBidAdapter.js +++ b/modules/seedtagBidAdapter.js @@ -18,8 +18,8 @@ function mapMediaType(seedtagMediaType) { else return seedtagMediaType; } -function getMediaTypeFromBid(bid) { - return bid.mediaTypes && Object.keys(bid.mediaTypes)[0] +function hasVideoMediaType(bid) { + return !!bid.mediaTypes && !!bid.mediaTypes.video } function hasMandatoryParams(params) { @@ -34,7 +34,7 @@ function hasMandatoryParams(params) { ); } -function hasVideoMandatoryParams(mediaTypes) { +function hasMandatoryVideoParams(mediaTypes) { const isVideoInStream = !!mediaTypes.video && mediaTypes.video.context === 'instream'; const isPlayerSize = @@ -65,7 +65,7 @@ function buildBidRequests(validBidRequests) { bidRequest.adPosition = params.adPosition; } - if (params.video) { + if (hasVideoMediaType(validBidRequest)) { bidRequest.videoParams = params.video || {}; bidRequest.videoParams.w = validBidRequest.mediaTypes.video.playerSize[0][0]; @@ -124,8 +124,8 @@ export const spec = { * @return boolean True if this is a valid bid, and false otherwise. */ isBidRequestValid(bid) { - return getMediaTypeFromBid(bid) === VIDEO - ? hasMandatoryParams(bid.params) && hasVideoMandatoryParams(bid.mediaTypes) + return hasVideoMediaType(bid) + ? hasMandatoryParams(bid.params) && hasMandatoryVideoParams(bid.mediaTypes) : hasMandatoryParams(bid.params); }, diff --git a/test/spec/modules/seedtagBidAdapter_spec.js b/test/spec/modules/seedtagBidAdapter_spec.js index 5c8c58196f7..8957bde6bd9 100644 --- a/test/spec/modules/seedtagBidAdapter_spec.js +++ b/test/spec/modules/seedtagBidAdapter_spec.js @@ -1,6 +1,9 @@ import { expect } from 'chai' import { spec, getTimeoutUrl } from 'modules/seedtagBidAdapter.js' +const PUBLISHER_ID = '0000-0000-01' +const ADUNIT_ID = '000000' + function getSlotConfigs(mediaTypes, params) { return { params: params, @@ -16,10 +19,16 @@ function getSlotConfigs(mediaTypes, params) { } } +function createVideoSlotConfig(mediaType) { + return getSlotConfigs(mediaType, { + publisherId: PUBLISHER_ID, + adUnitId: ADUNIT_ID, + placement: 'video' + }) +} + describe('Seedtag Adapter', function() { describe('isBidRequestValid method', function() { - const PUBLISHER_ID = '0000-0000-01' - const ADUNIT_ID = '000000' describe('returns true', function() { describe('when banner slot config has all mandatory params', () => { describe('and placement has the correct value', function() { @@ -66,13 +75,13 @@ describe('Seedtag Adapter', function() { }) describe('returns false', function() { describe('when params are not correct', function() { - function createSlotconfig(params) { + function createSlotConfig(params) { return getSlotConfigs({ banner: {} }, params) } it('does not have the PublisherToken.', function() { const isBidRequestValid = spec.isBidRequestValid( - createSlotconfig({ - adUnitId: '000000', + createSlotConfig({ + adUnitId: ADUNIT_ID, placement: 'banner' }) ) @@ -80,8 +89,8 @@ describe('Seedtag Adapter', function() { }) it('does not have the AdUnitId.', function() { const isBidRequestValid = spec.isBidRequestValid( - createSlotconfig({ - publisherId: '0000-0000-01', + createSlotConfig({ + publisherId: PUBLISHER_ID, placement: 'banner' }) ) @@ -89,18 +98,18 @@ describe('Seedtag Adapter', function() { }) it('does not have the placement.', function() { const isBidRequestValid = spec.isBidRequestValid( - createSlotconfig({ - publisherId: '0000-0000-01', - adUnitId: '000000' + createSlotConfig({ + publisherId: PUBLISHER_ID, + adUnitId: ADUNIT_ID }) ) expect(isBidRequestValid).to.equal(false) }) it('does not have a the correct placement.', function() { const isBidRequestValid = spec.isBidRequestValid( - createSlotconfig({ - publisherId: '0000-0000-01', - adUnitId: '000000', + createSlotConfig({ + publisherId: PUBLISHER_ID, + adUnitId: ADUNIT_ID, placement: 'another_thing' }) ) @@ -117,19 +126,19 @@ describe('Seedtag Adapter', function() { } it('is a void object', function() { const isBidRequestValid = spec.isBidRequestValid( - createVideoSlotconfig({ video: {} }) + createVideoSlotConfig({ video: {} }) ) expect(isBidRequestValid).to.equal(false) }) it('does not have playerSize.', function() { const isBidRequestValid = spec.isBidRequestValid( - createVideoSlotconfig({ video: { context: 'instream' } }) + createVideoSlotConfig({ video: { context: 'instream' } }) ) expect(isBidRequestValid).to.equal(false) }) it('is not instream ', function() { const isBidRequestValid = spec.isBidRequestValid( - createVideoSlotconfig({ + createVideoSlotConfig({ video: { context: 'outstream', playerSize: [[600, 200]] @@ -138,6 +147,20 @@ describe('Seedtag Adapter', function() { ) expect(isBidRequestValid).to.equal(false) }) + describe('order does not matter', function() { + it('when video is not the first slot', function() { + const isBidRequestValid = spec.isBidRequestValid( + createVideoSlotConfig({ banner: {}, video: {} }) + ) + expect(isBidRequestValid).to.equal(false) + }) + it('when video is the first slot', function() { + const isBidRequestValid = spec.isBidRequestValid( + createVideoSlotConfig({ video: {}, banner: {} }) + ) + expect(isBidRequestValid).to.equal(false) + }) + }) }) }) }) @@ -148,8 +171,8 @@ describe('Seedtag Adapter', function() { timeout: 1000 } const mandatoryParams = { - publisherId: '0000-0000-01', - adUnitId: '000000', + publisherId: PUBLISHER_ID, + adUnitId: ADUNIT_ID, placement: 'banner' } const inStreamParams = Object.assign({}, mandatoryParams, { From 3c6e07508071b8cf54c66a049965f0cee40dba18 Mon Sep 17 00:00:00 2001 From: Eric Nolte Date: Fri, 11 Sep 2020 04:14:03 -0400 Subject: [PATCH 31/34] add verizon alias to aol (#5722) * add verizon alias to aol * Update aolBidAdapter.js --- modules/aolBidAdapter.js | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/modules/aolBidAdapter.js b/modules/aolBidAdapter.js index d7ff7453870..1f43231e495 100644 --- a/modules/aolBidAdapter.js +++ b/modules/aolBidAdapter.js @@ -4,6 +4,7 @@ import { BANNER } from '../src/mediaTypes.js'; const AOL_BIDDERS_CODES = { AOL: 'aol', + VERIZON: 'verizon', ONEMOBILE: 'onemobile', ONEDISPLAY: 'onedisplay' }; @@ -48,10 +49,10 @@ const NUMERIC_VALUES = { }; function template(strings, ...keys) { - return function(...values) { + return function (...values) { let dict = values[values.length - 1] || {}; let result = [strings[0]]; - keys.forEach(function(key, i) { + keys.forEach(function (key, i) { let value = utils.isInteger(key) ? values[key] : dict[key]; result.push(value, strings[i + 1]); }); @@ -59,12 +60,16 @@ function template(strings, ...keys) { }; } -function _isMarketplaceBidder(bidder) { - return bidder === AOL_BIDDERS_CODES.AOL || bidder === AOL_BIDDERS_CODES.ONEDISPLAY; +function _isMarketplaceBidder(bidderCode) { + return bidderCode === AOL_BIDDERS_CODES.AOL || + bidderCode === AOL_BIDDERS_CODES.VERIZON || + bidderCode === AOL_BIDDERS_CODES.ONEDISPLAY; } function _isOneMobileBidder(bidderCode) { - return bidderCode === AOL_BIDDERS_CODES.AOL || bidderCode === AOL_BIDDERS_CODES.ONEMOBILE; + return bidderCode === AOL_BIDDERS_CODES.AOL || + bidderCode === AOL_BIDDERS_CODES.VERIZON || + bidderCode === AOL_BIDDERS_CODES.ONEMOBILE; } function _isNexageRequestPost(bid) { @@ -101,7 +106,11 @@ function resolveEndpointCode(bid) { export const spec = { code: AOL_BIDDERS_CODES.AOL, gvlid: 25, - aliases: [AOL_BIDDERS_CODES.ONEMOBILE, AOL_BIDDERS_CODES.ONEDISPLAY], + aliases: [ + AOL_BIDDERS_CODES.ONEMOBILE, + AOL_BIDDERS_CODES.ONEDISPLAY, + AOL_BIDDERS_CODES.VERIZON + ], supportedMediaTypes: [BANNER], isBidRequestValid(bid) { return isMarketplaceBid(bid) || isMobileBid(bid); @@ -121,7 +130,7 @@ export const spec = { } }); }, - interpretResponse({body}, bidRequest) { + interpretResponse({ body }, bidRequest) { if (!body) { utils.logError('Empty bid response', bidRequest.bidderCode, body); } else { @@ -216,11 +225,11 @@ export const spec = { })); }, buildOneMobileGetUrl(bid, consentData) { - let {dcn, pos, ext} = bid.params; + let { dcn, pos, ext } = bid.params; let nexageApi = this.buildOneMobileBaseUrl(bid); if (dcn && pos) { let dynamicParams = this.formatOneMobileDynamicParams(ext, consentData); - nexageApi += nexageGetApiTemplate({dcn, pos, dynamicParams}); + nexageApi += nexageGetApiTemplate({ dcn, pos, dynamicParams }); } return nexageApi; }, From ba8ef8624aec1247df9f0f2df4815836d82faa07 Mon Sep 17 00:00:00 2001 From: Dmitriy Labuzov Date: Fri, 11 Sep 2020 12:25:30 +0300 Subject: [PATCH 32/34] Add prebid version to ad-server call (#5730) Co-authored-by: Dmitriy Labuzov --- modules/yieldmoBidAdapter.js | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/yieldmoBidAdapter.js b/modules/yieldmoBidAdapter.js index 829b573ffd9..08dc3189eda 100644 --- a/modules/yieldmoBidAdapter.js +++ b/modules/yieldmoBidAdapter.js @@ -27,6 +27,7 @@ export const spec = { */ buildRequests: function (bidRequests, bidderRequest) { let serverRequest = { + pbav: '$prebid.version$', p: [], page_url: bidderRequest.refererInfo.referer, bust: new Date().getTime().toString(), From 1e9be731d43a5c9ce7734f48eb967c1adb4c2c2e Mon Sep 17 00:00:00 2001 From: shikharsharma-zeotap Date: Fri, 11 Sep 2020 21:45:07 +0530 Subject: [PATCH 33/34] Zeotap ID+ submodule (#5640) * IDU-117 IDU-119 Add zeotap submodule * IDU-117 IDU-119 Add tests for zeotapId+ module * add zeotapId+ module spec * Add IDP base64 decode logic * remove unwanted file changes * rename zeotapId+ to zeotapIdPlus * add zeotapIdPlus submodule to submodules.json * refactor code for requested changes: remove storage from configParams * add tests to eids_spec * rebase n resolve conflicts --- integrationExamples/gpt/userId_example.html | 3 + modules/.submodules.json | 3 +- modules/userId/eids.js | 5 + modules/userId/eids.md | 9 +- modules/zeotapIdPlusIdSystem.js | 52 +++++++ test/spec/modules/eids_spec.js | 14 ++ test/spec/modules/userId_spec.js | 87 ++++++++--- .../spec/modules/zeotapIdPlusIdSystem_spec.js | 141 ++++++++++++++++++ 8 files changed, 292 insertions(+), 22 deletions(-) create mode 100644 modules/zeotapIdPlusIdSystem.js create mode 100644 test/spec/modules/zeotapIdPlusIdSystem_spec.js diff --git a/integrationExamples/gpt/userId_example.html b/integrationExamples/gpt/userId_example.html index 8115e60fcd1..51b9f2aef90 100644 --- a/integrationExamples/gpt/userId_example.html +++ b/integrationExamples/gpt/userId_example.html @@ -230,6 +230,9 @@ name: "_li_pbid", expires: 28 } + }, + { + name: "zeotapIdPlus" }], syncDelay: 5000, auctionDelay: 1000 diff --git a/modules/.submodules.json b/modules/.submodules.json index 50d17fc5f6c..bacc543401f 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -12,7 +12,8 @@ "netIdSystem", "identityLinkIdSystem", "sharedIdSystem", - "intentIqIdSystem" + "intentIqIdSystem", + "zeotapIdPlusIdSystem" ], "adpod": [ "freeWheelAdserverVideo", diff --git a/modules/userId/eids.js b/modules/userId/eids.js index 15399b9b980..e6c3dbd5bd8 100644 --- a/modules/userId/eids.js +++ b/modules/userId/eids.js @@ -123,6 +123,11 @@ const USER_IDS_CONFIG = { third: data.third } : undefined; } + }, + // zeotapIdPlus + 'IDP': { + source: 'zeotap.com', + atype: 1 } }; diff --git a/modules/userId/eids.md b/modules/userId/eids.md index 846b9b19207..fc46fef7b97 100644 --- a/modules/userId/eids.md +++ b/modules/userId/eids.md @@ -95,6 +95,13 @@ userIdAsEids = [ third: 'some-random-id-value' } }] - } + }, + { + source: 'zeotap.com', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] + }, ] ``` diff --git a/modules/zeotapIdPlusIdSystem.js b/modules/zeotapIdPlusIdSystem.js new file mode 100644 index 00000000000..c194a9b9679 --- /dev/null +++ b/modules/zeotapIdPlusIdSystem.js @@ -0,0 +1,52 @@ +/** + * This module adds Zeotap to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/zeotapIdPlusIdSystem + * @requires module:modules/userId + */ +import * as utils from '../src/utils.js' +import {submodule} from '../src/hook.js'; +import { getStorageManager } from '../src/storageManager.js'; + +const ZEOTAP_COOKIE_NAME = 'IDP'; +const storage = getStorageManager(); + +function readCookie() { + return storage.cookiesAreEnabled ? storage.getCookie(ZEOTAP_COOKIE_NAME) : null; +} + +function readFromLocalStorage() { + return storage.localStorageIsEnabled ? storage.getDataFromLocalStorage(ZEOTAP_COOKIE_NAME) : null; +} + +/** @type {Submodule} */ +export const zeotapIdPlusSubmodule = { + /** + * used to link submodule with config + * @type {string} + */ + name: 'zeotapIdPlus', + /** + * decode the stored id value for passing to bid requests + * @function + * @param { Object | string | undefined } value + * @return { Object | string | undefined } + */ + decode(value) { + const id = value ? utils.isStr(value) ? value : utils.isPlainObject(value) ? value.id : undefined : undefined; + return id ? { + 'IDP': JSON.parse(atob(id)) + } : undefined; + }, + /** + * performs action to obtain id and return a value in the callback's response argument + * @function + * @param {SubmoduleParams} configParams + * @return {{id: string | undefined} | undefined} + */ + getId() { + const id = readCookie() || readFromLocalStorage(); + return id ? { id } : undefined; + } +}; +submodule('userId', zeotapIdPlusSubmodule); diff --git a/test/spec/modules/eids_spec.js b/test/spec/modules/eids_spec.js index 8ad44f0b1ad..a0bc0a84ee3 100644 --- a/test/spec/modules/eids_spec.js +++ b/test/spec/modules/eids_spec.js @@ -192,6 +192,20 @@ describe('eids array generation for known sub-modules', function() { }] }); }); + it('zeotapIdPlus', function() { + const userId = { + IDP: 'some-random-id-value' + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'zeotap.com', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] + }); + }); }); describe('Negative case', function() { diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index 5ac68de345d..167187a281f 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -26,6 +26,7 @@ import {liveIntentIdSubmodule} from 'modules/liveIntentIdSystem.js'; import {merkleIdSubmodule} from 'modules/merkleIdSystem.js'; import {netIdSubmodule} from 'modules/netIdSystem.js'; import {intentIqIdSubmodule} from 'modules/intentIqIdSystem.js'; +import {zeotapIdPlusSubmodule} from 'modules/zeotapIdPlusIdSystem.js'; import {sharedIdSubmodule} from 'modules/sharedIdSystem.js'; import {server} from 'test/mocks/xhr.js'; @@ -354,7 +355,7 @@ describe('User ID', function() { }); it('handles config with no usersync object', function() { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule]); init(config); config.setConfig({}); // usersync is undefined, and no logInfo message for 'User ID - usersync config updated' @@ -362,14 +363,14 @@ describe('User ID', function() { }); it('handles config with empty usersync object', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule]); init(config); config.setConfig({userSync: {}}); expect(typeof utils.logInfo.args[0]).to.equal('undefined'); }); it('handles config with usersync and userIds that are empty objs', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule]); init(config); config.setConfig({ userSync: { @@ -380,7 +381,7 @@ describe('User ID', function() { }); it('handles config with usersync and userIds with empty names or that dont match a submodule.name', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule]); init(config); config.setConfig({ userSync: { @@ -397,15 +398,15 @@ describe('User ID', function() { }); it('config with 1 configurations should create 1 submodules', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule]); init(config); config.setConfig(getConfigMock(['unifiedId', 'unifiedid', 'cookie'])); expect(utils.logInfo.args[0][0]).to.exist.and.to.equal('User ID - usersync config updated for 1 submodules'); }); - it('config with 9 configurations should result in 9 submodules add', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, liveIntentIdSubmodule, britepoolIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule]); + it('config with 10 configurations should result in 10 submodules add', function () { + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, liveIntentIdSubmodule, britepoolIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule]); init(config); config.setConfig({ userSync: { @@ -436,14 +437,16 @@ describe('User ID', function() { }, { name: 'intentIqId', storage: { name: 'intentIqId', type: 'cookie' } + }, { + name: 'zeotapIdPlus' }] } }); - expect(utils.logInfo.args[0][0]).to.exist.and.to.equal('User ID - usersync config updated for 9 submodules'); + expect(utils.logInfo.args[0][0]).to.exist.and.to.equal('User ID - usersync config updated for 10 submodules'); }); it('config syncDelay updates module correctly', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule]); init(config); config.setConfig({ userSync: { @@ -458,7 +461,7 @@ describe('User ID', function() { }); it('config auctionDelay updates module correctly', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule]); init(config); config.setConfig({ userSync: { @@ -473,7 +476,7 @@ describe('User ID', function() { }); it('config auctionDelay defaults to 0 if not a number', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule]); init(config); config.setConfig({ userSync: { @@ -1142,7 +1145,31 @@ describe('User ID', function() { }, {adUnits}); }); - it('test hook when pubCommonId, unifiedId, id5Id, identityLink, britepoolId, intentIqId, sharedId and netId have data to pass', function(done) { + it('test hook from zeotapIdPlus cookies', function(done) { + // simulate existing browser local storage values + coreStorage.setCookie('IDP', btoa(JSON.stringify('abcdefghijk')), (new Date(Date.now() + 5000).toUTCString())); + + setSubmoduleRegistry([zeotapIdPlusSubmodule]); + init(config); + config.setConfig(getConfigMock(['zeotapIdPlus', 'IDP', 'cookie'])); + + requestBidsHook(function() { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.IDP'); + expect(bid.userId.IDP).to.equal('abcdefghijk'); + expect(bid.userIdAsEids[0]).to.deep.equal({ + source: 'zeotap.com', + uids: [{id: 'abcdefghijk', atype: 1}] + }); + }); + }); + coreStorage.setCookie('IDP', '', EXPIRED_COOKIE_DATE); + done(); + }, {adUnits}); + }); + + it('test hook when pubCommonId, unifiedId, id5Id, identityLink, britepoolId, intentIqId, zeotapIdPlus, sharedId and netId have data to pass', function(done) { coreStorage.setCookie('pubcid', 'testpubcid', (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('unifiedid', JSON.stringify({'TDID': 'testunifiedid'}), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('id5id', JSON.stringify({'universal_uid': 'testid5id'}), (new Date(Date.now() + 5000).toUTCString())); @@ -1150,9 +1177,10 @@ describe('User ID', function() { coreStorage.setCookie('britepoolid', JSON.stringify({'primaryBPID': 'testbritepoolid'}), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('netId', JSON.stringify({'netId': 'testnetId'}), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('intentIqId', JSON.stringify({'ctrid': 'testintentIqId'}), (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('IDP', btoa(JSON.stringify('zeotapId')), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('sharedid', JSON.stringify({'id': 'test_sharedId', 'ts': 1590525289611}), (new Date(Date.now() + 5000).toUTCString())); - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, britepoolIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, britepoolIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule]); init(config); config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'], ['unifiedId', 'unifiedid', 'cookie'], @@ -1161,7 +1189,8 @@ describe('User ID', function() { ['britepoolId', 'britepoolid', 'cookie'], ['netId', 'netId', 'cookie'], ['sharedId', 'sharedid', 'cookie'], - ['intentIqId', 'intentIqId', 'cookie'])); + ['intentIqId', 'intentIqId', 'cookie'], + ['zeotapIdPlus', 'IDP', 'cookie'])); requestBidsHook(function() { adUnits.forEach(unit => { @@ -1192,7 +1221,10 @@ describe('User ID', function() { // also check that intentIqId id data was copied to bid expect(bid).to.have.deep.nested.property('userId.intentIqId'); expect(bid.userId.intentIqId).to.equal('testintentIqId'); - expect(bid.userIdAsEids.length).to.equal(8); + // also check that zeotapIdPlus id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.IDP'); + expect(bid.userId.IDP).to.equal('zeotapId'); + expect(bid.userIdAsEids.length).to.equal(9); }); }); coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); @@ -1203,11 +1235,12 @@ describe('User ID', function() { coreStorage.setCookie('netId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('sharedid', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('intentIqId', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('IDP', '', EXPIRED_COOKIE_DATE); done(); }, {adUnits}); }); - it('test hook when pubCommonId, unifiedId, id5Id, britepoolId, intentIqId, sharedId and netId have their modules added before and after init', function(done) { + it('test hook when pubCommonId, unifiedId, id5Id, britepoolId, intentIqId, zeotapIdPlus, sharedId and netId have their modules added before and after init', function(done) { coreStorage.setCookie('pubcid', 'testpubcid', (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('unifiedid', JSON.stringify({'TDID': 'cookie-value-add-module-variations'}), new Date(Date.now() + 5000).toUTCString()); coreStorage.setCookie('id5id', JSON.stringify({'universal_uid': 'testid5id'}), (new Date(Date.now() + 5000).toUTCString())); @@ -1216,6 +1249,7 @@ describe('User ID', function() { coreStorage.setCookie('netId', JSON.stringify({'netId': 'testnetId'}), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('sharedid', JSON.stringify({'id': 'test_sharedId', 'ts': 1590525289611}), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('intentIqId', JSON.stringify({'ctrid': 'testintentIqId'}), (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('IDP', btoa(JSON.stringify('zeotapId')), (new Date(Date.now() + 5000).toUTCString())); setSubmoduleRegistry([]); @@ -1232,6 +1266,7 @@ describe('User ID', function() { attachIdSystem(netIdSubmodule); attachIdSystem(sharedIdSubmodule); attachIdSystem(intentIqIdSubmodule); + attachIdSystem(zeotapIdPlusSubmodule); config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'], ['unifiedId', 'unifiedid', 'cookie'], @@ -1240,7 +1275,8 @@ describe('User ID', function() { ['britepoolId', 'britepoolid', 'cookie'], ['netId', 'netId', 'cookie'], ['sharedId', 'sharedid', 'cookie'], - ['intentIqId', 'intentIqId', 'cookie'])); + ['intentIqId', 'intentIqId', 'cookie'], + ['zeotapIdPlus', 'IDP', 'cookie'])); requestBidsHook(function() { adUnits.forEach(unit => { @@ -1271,7 +1307,10 @@ describe('User ID', function() { // also check that intentIqId id data was copied to bid expect(bid).to.have.deep.nested.property('userId.intentIqId'); expect(bid.userId.intentIqId).to.equal('testintentIqId'); - expect(bid.userIdAsEids.length).to.equal(8); + // also check that zeotapIdPlus id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.IDP'); + expect(bid.userId.IDP).to.equal('zeotapId'); + expect(bid.userIdAsEids.length).to.equal(9); }); }); coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); @@ -1282,6 +1321,7 @@ describe('User ID', function() { coreStorage.setCookie('netId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('sharedid', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('intentIqId', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('IDP', '', EXPIRED_COOKIE_DATE); done(); }, {adUnits}); }); @@ -1316,9 +1356,10 @@ describe('User ID', function() { coreStorage.setCookie('netId', JSON.stringify({'netId': 'testnetId'}), new Date(Date.now() + 5000).toUTCString()); coreStorage.setCookie('sharedid', JSON.stringify({'id': 'test_sharedId', 'ts': 1590525289611}), new Date(Date.now() + 5000).toUTCString()); coreStorage.setCookie('intentIqId', JSON.stringify({'ctrid': 'testintentIqId'}), (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('IDP', btoa(JSON.stringify('zeotapId')), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('MOCKID', JSON.stringify({'MOCKID': '123456778'}), new Date(Date.now() + 5000).toUTCString()); - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, britepoolIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, britepoolIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule]); init(config); config.setConfig({ @@ -1340,6 +1381,8 @@ describe('User ID', function() { name: 'sharedId', storage: {name: 'sharedid', type: 'cookie'} }, { name: 'intentIqId', storage: { name: 'intentIqId', type: 'cookie' } + }, { + name: 'zeotapIdPlus' }, { name: 'mockId', storage: {name: 'MOCKID', type: 'cookie'} }] @@ -1393,7 +1436,10 @@ describe('User ID', function() { // also check that intentIqId id data was copied to bid expect(bid).to.have.deep.nested.property('userId.intentIqId'); expect(bid.userId.intentIqId).to.equal('testintentIqId'); - expect(bid.userIdAsEids.length).to.equal(8); + // also check that zeotapIdPlus id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.IDP'); + expect(bid.userId.IDP).to.equal('zeotapId'); + expect(bid.userIdAsEids.length).to.equal(9); }); }); coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); @@ -1404,6 +1450,7 @@ describe('User ID', function() { coreStorage.setCookie('netId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('sharedid', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('intentIqId', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('IDP', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('MOCKID', '', EXPIRED_COOKIE_DATE); done(); }, {adUnits}); diff --git a/test/spec/modules/zeotapIdPlusIdSystem_spec.js b/test/spec/modules/zeotapIdPlusIdSystem_spec.js new file mode 100644 index 00000000000..52698ecffc9 --- /dev/null +++ b/test/spec/modules/zeotapIdPlusIdSystem_spec.js @@ -0,0 +1,141 @@ +import { expect } from 'chai'; +import find from 'core-js-pure/features/array/find.js'; +import { config } from 'src/config.js'; +import { newStorageManager } from 'src/storageManager.js'; +import { init, requestBidsHook, setSubmoduleRegistry } from 'modules/userId/index.js'; +import { zeotapIdPlusSubmodule } from 'modules/zeotapIdPlusIdSystem.js'; + +const storage = newStorageManager(); + +const ZEOTAP_COOKIE_NAME = 'IDP'; +const ZEOTAP_COOKIE = 'THIS-IS-A-DUMMY-COOKIE'; +const ENCODED_ZEOTAP_COOKIE = btoa(JSON.stringify(ZEOTAP_COOKIE)); + +function getConfigMock() { + return { + userSync: { + syncDelay: 0, + userIds: [{ + name: 'zeotapIdPlus' + }] + } + } +} + +function getAdUnitMock(code = 'adUnit-code') { + return { + code, + mediaTypes: {banner: {}, native: {}}, + sizes: [ + [300, 200], + [300, 600] + ], + bids: [{ + bidder: 'sampleBidder', + params: { placementId: 'banner-only-bidder' } + }] + }; +} + +function unsetCookie() { + storage.setCookie(ZEOTAP_COOKIE_NAME, ''); +} + +function unsetLocalStorage() { + storage.setDataInLocalStorage(ZEOTAP_COOKIE_NAME, ''); +} + +describe('Zeotap ID System', function() { + describe('test method: getId', function() { + afterEach(() => { + unsetCookie(); + unsetLocalStorage(); + }) + + it('provides the stored Zeotap id if a cookie exists', function() { + storage.setCookie( + ZEOTAP_COOKIE_NAME, + ENCODED_ZEOTAP_COOKIE, + (new Date(Date.now() + 5000).toUTCString()), + ); + let id = zeotapIdPlusSubmodule.getId(); + expect(id).to.deep.equal({ + id: ENCODED_ZEOTAP_COOKIE + }); + }); + + it('provides the stored Zeotap id if cookie is absent but present in local storage', function() { + storage.setDataInLocalStorage(ZEOTAP_COOKIE_NAME, ENCODED_ZEOTAP_COOKIE); + let id = zeotapIdPlusSubmodule.getId(); + expect(id).to.deep.equal({ + id: ENCODED_ZEOTAP_COOKIE + }); + }); + + it('returns undefined if both cookie and local storage are empty', function() { + let id = zeotapIdPlusSubmodule.getId(); + expect(id).to.be.undefined + }) + }); + + describe('test method: decode', function() { + it('provides the Zeotap ID (IDP) from a stored object', function() { + let zeotapId = { + id: ENCODED_ZEOTAP_COOKIE, + }; + + expect(zeotapIdPlusSubmodule.decode(zeotapId)).to.deep.equal({ + IDP: ZEOTAP_COOKIE + }); + }); + + it('provides the Zeotap ID (IDP) from a stored string', function() { + let zeotapId = ENCODED_ZEOTAP_COOKIE; + + expect(zeotapIdPlusSubmodule.decode(zeotapId)).to.deep.equal({ + IDP: ZEOTAP_COOKIE + }); + }); + }); + + describe('requestBids hook', function() { + let adUnits; + + beforeEach(function() { + adUnits = [getAdUnitMock()]; + storage.setCookie( + ZEOTAP_COOKIE_NAME, + ENCODED_ZEOTAP_COOKIE, + (new Date(Date.now() + 5000).toUTCString()), + ); + setSubmoduleRegistry([zeotapIdPlusSubmodule]); + init(config); + config.setConfig(getConfigMock()); + }); + + afterEach(function() { + unsetCookie(); + unsetLocalStorage(); + }); + + it('when a stored Zeotap ID exists it is added to bids', function(done) { + requestBidsHook(function() { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.IDP'); + expect(bid.userId.IDP).to.equal(ZEOTAP_COOKIE); + const zeotapIdAsEid = find(bid.userIdAsEids, e => e.source == 'zeotap.com'); + expect(zeotapIdAsEid).to.deep.equal({ + source: 'zeotap.com', + uids: [{ + id: ZEOTAP_COOKIE, + atype: 1, + }] + }); + }); + }); + done(); + }, { adUnits }); + }); + }); +}); From 896cc0f00e9c0fe2df4cc30310b589b1fc8854c6 Mon Sep 17 00:00:00 2001 From: bretg Date: Fri, 11 Sep 2020 15:19:12 -0400 Subject: [PATCH 34/34] Prebid Server returns exp rather than ttl (#5715) Updating how pbsBidAdapter sets the "TTL" for bids. TTL in PBJS terms is how long the bid can stay in cache. However, the OpenRTB standard location for this value is `exp`. --- modules/prebidServerBidAdapter/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index 0ff967f1da9..b153d0bf8db 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -825,9 +825,9 @@ const OPEN_RTB_PROTOCOL = { bidObject.meta = bidObject.meta || {}; if (bid.adomain) { bidObject.meta.advertiserDomains = bid.adomain; } - // TODO: Remove when prebid-server returns ttl and netRevenue const configTtl = _s2sConfig.defaultTtl || DEFAULT_S2S_TTL; - bidObject.ttl = (bid.ttl) ? bid.ttl : configTtl; + // the OpenRTB location for "TTL" as understood by Prebid.js is "exp" (expiration). + bidObject.ttl = (bid.exp) ? bid.exp : configTtl; bidObject.netRevenue = (bid.netRevenue) ? bid.netRevenue : DEFAULT_S2S_NETREVENUE; bids.push({ adUnit: bid.impid, bid: bidObject });