From 38e1ade765cf96edcce063664b52b37fcc98c531 Mon Sep 17 00:00:00 2001 From: Josh Date: Mon, 22 Feb 2021 10:30:26 -0800 Subject: [PATCH 1/8] Initial nativoBidAdapter document creation (js, md and spec) --- modules/nativoBidAdapter.js | 194 +++++++++++++++++++++ modules/nativoBidAdapter.md | 54 ++++++ package-lock.json | 13 +- test/spec/modules/nativoBidAdapter_spec.js | 158 +++++++++++++++++ 4 files changed, 408 insertions(+), 11 deletions(-) create mode 100644 modules/nativoBidAdapter.js create mode 100644 modules/nativoBidAdapter.md create mode 100644 test/spec/modules/nativoBidAdapter_spec.js diff --git a/modules/nativoBidAdapter.js b/modules/nativoBidAdapter.js new file mode 100644 index 00000000000..bb78ff06d79 --- /dev/null +++ b/modules/nativoBidAdapter.js @@ -0,0 +1,194 @@ +import * as utils from '../src/utils.js' +import { registerBidder } from '../src/adapters/bidderFactory.js' +import { BANNER } from '../src/mediaTypes.js' +// import { config } from 'src/config' + +const BIDDER_CODE = 'nativo' +const BIDDER_ENDPOINT = 'http://www.testlocalbidrequest.com:3000/requestBid/' // test local endpoint +const USER_SYNC_URL_IFRAME = 'http://www.testlocalbidrequest.com:3000/' +const USER_SYNC_URL_IMAGE = 'http://www.testlocalbidrequest.com:3000/' + +const TIME_TO_LIVE = 360 + +const SUPPORTED_AD_TYPES = [BANNER] + +// Prebid adapter referrence doc: https://docs.prebid.org/dev-docs/bidder-adaptor.html + +export const spec = { + code: BIDDER_CODE, + aliases: ['ntv'], // short code + supportedMediaTypes: SUPPORTED_AD_TYPES, + + /** + * 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.adUnitCode) + }, + + /** + * Called when the page asks Prebid.js for bids + * Make a server request from the list of BidRequests + * + * @param {Array} validBidRequests - An array of bidRequest objects, one for each AdUnit that your module is involved in. This array has been processed for special features like sizeConfig, so it’s the list that you should be looping through + * @param {Object} bidderRequest - The master bidRequest object. This object is useful because it carries a couple of bid parameters that are global to all the bids. + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function (validBidRequests, bidderRequest) { + const payload = { + selector: validBidRequests[0].adUnitCode, + id: validBidRequests[0].bidId + } + const payloadString = JSON.stringify(payload); + + let serverRequest = { + method: 'POST', + url: BIDDER_ENDPOINT, + data: payloadString + } + + return serverRequest + }, + + /** + * Will be called when the browser has received the response from your server. + * The function will parse the response and create a bidResponse object containing one or more bids. + * The adapter should indicate no valid bids by returning an empty array. + * + * @param {Object} response - Data returned from the bidding server request endpoint + * @param {Object} request - The request object used to call the server request endpoint + * @return {Array} An array of bids which were nested inside the server. + */ + interpretResponse: function (response, request) { + // If the bid response was empty, return [] + if (!response || !response.body || utils.isEmpty(response.body)) return [] + + try { + // Parse the response and return a bidResponses array + const body = response.body + // const headerValue = response.headers.get('some-response-header') + const bidResponses = [] + + let seatbids = body.seatbid + // Step through and grab pertinent data + let bidResponse + seatbids.forEach((seatbid) => { + seatbid.bid.forEach((bid) => { + bidResponse = { + requestId: body.id, + cpm: bid.price, + currency: body.cur, + width: bid.w, + height: bid.h, + creativeId: bid.crid, + dealId: bid.id, + netRevenue: true, + ttl: bid.ttl || TIME_TO_LIVE, + ad: bid.adm, + meta: { + advertiserDomains: bid.adomain, + // cat: bid.cat, + // impid: bid.impid, + // networkId: NETWORK_ID, + // networkName: NETWORK_NAME, + // agencyId: AGENCY_ID, + // agencyName: AGENCY_NAME, + // advertiserId: ADVERTISER_ID, + // advertiserName: ADVERTISER_NAME, + // advertiserDomains: [ARRAY_OF_ADVERTISER_DOMAINS], + // brandId: BRAND_ID, + // brandName: BRAND_NAME, + // primaryCatId: IAB_CATEGORY, + // secondaryCatIds: [ARRAY_OF_IAB_CATEGORIES], + // mediaType: MEDIA_TYPE + }, + } + + bidResponses.push(bidResponse) + }) + }) + + return bidResponses + } catch (error) { + // If there is an error, return [] + return [] + } + }, + + /** + * All user ID sync activity should be done using the getUserSyncs callback of the BaseAdapter model. + * Given an array of all the responses from the server, getUserSyncs is used to determine which user syncs should occur. + * The order of syncs in the serverResponses array matters. The most important ones should come first, since publishers may limit how many are dropped on their page. + * @param {Object} syncOptions - Which user syncs are allowed? + * @param {Array} serverResponses - Array of server's responses + * @param {Object} gdprConsent - GDPR consent data + * @param {Object} uspConsent - USP consent data + * @return {Array} The user syncs which should be dropped. + */ + getUserSyncs: function ( + syncOptions, + serverResponses, + gdprConsent, + uspConsent + ) { + // console.log('syncOptions', syncOptions) // eslint-disable-line no-console + // console.log('serverResponses', serverResponses) // eslint-disable-line no-console + // console.log('gdprConsent', gdprConsent) // eslint-disable-line no-console + // console.log('uspConsent', uspConsent) // eslint-disable-line no-console + + const syncs = [] + + let params = '' + + // GDPR + if (gdprConsent) { + params += '&gdpr=' + (gdprConsent.gdprApplies ? 1 : 0) + params += + '&gdpr_consent=' + encodeURIComponent(gdprConsent.consentString || '') + } + + // CCPA + if (uspConsent) { + params += '&us_privacy=' + encodeURIComponent(uspConsent) + } + + // TODO: We need to determine how we're including sync urls + if (syncOptions.iframeEnabled) { + syncs.push({ + type: 'iframe', + url: USER_SYNC_URL_IFRAME + params, + }) + } + if (syncOptions.pixelEnabled) { + syncs.push({ + type: 'image', + url: USER_SYNC_URL_IMAGE + params, + }) + } + + return syncs + }, + + /** + * Will be called when an adpater timed out for an auction. + * Adapter can fire a ajax or pixel call to register a timeout at thier end. + * @param {Object} timeoutData - Timeout specific data + */ + onTimeout: function (timeoutData) {}, + + /** + * Will be called when a bid from the adapter won the auction. + * @param {Object} bid - The bid that won the auction + */ + onBidWon: function (bid) {}, + + /** + * Will be called when the adserver targeting has been set for a bid from the adapter. + * @param {Object} bidder - The bid of which the targeting has been set + */ + onSetTargeting: function (bid) {}, +} +registerBidder(spec) diff --git a/modules/nativoBidAdapter.md b/modules/nativoBidAdapter.md new file mode 100644 index 00000000000..1ab7e1c7e3c --- /dev/null +++ b/modules/nativoBidAdapter.md @@ -0,0 +1,54 @@ +# Overview + +``` +Module Name: Nativo Bid Adapter +Module Type: Bidder Adapter +Maintainer: nativo@nativo.com +``` + +# Description + +Module that connects to Nativo's demand sources + +# Dev + +gulp serve --modules=nativoBidAdapter + +# Test Parameters + +``` +var adUnits = [ + { + code: 'test-div', + mediaTypes: { + banner: { + sizes: [[300, 250]], // a display size + } + }, + bids: [ + { + bidder: "nativo", + params: { + placement: '12345' + } + } + ] + },{ + code: 'test-div', + mediaTypes: { + banner: { + sizes: [[320, 50]], // a mobile size + } + }, + bids: [ + { + bidder: "nativo", + params: { + placement: 67890 + } + } + ] + } + ]; + +``` diff --git a/package-lock.json b/package-lock.json index 330298cbaac..3fe857a89e8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.15.0-pre", + "version": "4.27.0-pre", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -22437,7 +22437,6 @@ "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", "dev": true, - "optional": true, "requires": { "arr-flatten": "^1.1.0", "array-unique": "^0.3.2", @@ -22456,7 +22455,6 @@ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, - "optional": true, "requires": { "is-extendable": "^0.1.0" } @@ -22489,7 +22487,6 @@ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", "dev": true, - "optional": true, "requires": { "extend-shallow": "^2.0.1", "is-number": "^3.0.0", @@ -22502,7 +22499,6 @@ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, - "optional": true, "requires": { "is-extendable": "^0.1.0" } @@ -22557,15 +22553,13 @@ "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true, - "optional": true + "dev": true }, "is-number": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, - "optional": true, "requires": { "kind-of": "^3.0.2" }, @@ -22575,7 +22569,6 @@ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, - "optional": true, "requires": { "is-buffer": "^1.1.5" } @@ -22587,7 +22580,6 @@ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", "dev": true, - "optional": true, "requires": { "arr-diff": "^4.0.0", "array-unique": "^0.3.2", @@ -22637,7 +22629,6 @@ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", "dev": true, - "optional": true, "requires": { "is-number": "^3.0.0", "repeat-string": "^1.6.1" diff --git a/test/spec/modules/nativoBidAdapter_spec.js b/test/spec/modules/nativoBidAdapter_spec.js new file mode 100644 index 00000000000..01919554151 --- /dev/null +++ b/test/spec/modules/nativoBidAdapter_spec.js @@ -0,0 +1,158 @@ +import { expect } from 'chai' +import { spec } from 'modules/nativoBidAdapter.js' +// import { newBidder } from 'src/adapters/bidderFactory.js' +// import * as bidderFactory from 'src/adapters/bidderFactory.js' +// import { deepClone } from 'src/utils.js' +// import { config } from 'src/config.js' + +describe('nativoBidAdapterTests', function () { + describe('isBidRequestValid', function () { + let bid = { + bidder: 'nativo', + params: { + placementId: '10433394', + }, + adUnitCode: 'adunit-code', + sizes: [ + [300, 250], + [300, 600], + ], + bidId: '27b02036ccfa6e', + bidderRequestId: '1372cd8bd8d6a8', + auctionId: 'cfc467e4-2707-48da-becb-bcaab0b2c114', + } + + 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.adUnitCode + bid.adUnitCode = 0 + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) + }) + + describe('buildRequests', function () { + let bidRequests = [ + { + bidder: 'nativo', + params: { + placementId: '10433394', + }, + adUnitCode: 'adunit-code', + sizes: [ + [300, 250], + [300, 600], + ], + bidId: '27b02036ccfa6e', + bidderRequestId: '1372cd8bd8d6a8', + auctionId: 'cfc467e4-2707-48da-becb-bcaab0b2c114', + transactionId: '3b36e7e0-0c3e-4006-a279-a741239154ff', + }, + ] + + it('should contain a valid selector', function () { + const request = spec.buildRequests(bidRequests) + const payload = JSON.parse(request.data) + + expect(payload.selector).to.exist + expect(payload.selector).to.be.a('string') + expect(payload.selector).to.not.have.lengthOf(0) + }) + }) +}) + +describe('interpretResponse', function () { + let response = { + id: '1F254428-AB11-4D5E-9887-567B3F952CA5', + seatbid: [ + { + seat: 'seat_0', + bid: [ + { + id: 'f70362ac-f3cf-4225-82a5-948b690927a6', + impid: '1', + price: 3.569, + adm: '', + h: 300, + w: 250, + cat: [], + adomain: ['test.com'], + crid: '1060_72_6760217', + }, + ], + }, + ], + cur: 'USD', + } + + it('should get correct bid response', function () { + let expectedResponse = [ + { + requestId: '1F254428-AB11-4D5E-9887-567B3F952CA5', + cpm: 3.569, + currency: 'USD', + width: 300, + height: 250, + creativeId: '1060_72_6760217', + dealId: 'f70362ac-f3cf-4225-82a5-948b690927a6', + netRevenue: true, + ttl: 360, + ad: '', + meta: { + advertiserDomains: ['test.com'], + }, + }, + ] + + let bidderRequest = { + bids: [ + { + adUnitCode: 'code', + }, + ], + } + let result = spec.interpretResponse({ body: response }, { bidderRequest }) + expect(Object.keys(result[0])).to.have.deep.members( + Object.keys(expectedResponse[0]) + ) + }) + + it('handles nobid responses', function () { + let response = {} + let bidderRequest + + let result = spec.interpretResponse({ body: response }, { bidderRequest }) + expect(result.length).to.equal(0) + }) +}) + +describe('getUserSyncs', function () { + const USER_SYNC_URL_IFRAME = 'http://www.testlocalbidrequest.com:3000/' + const USER_SYNC_URL_IMAGE = 'http://www.testlocalbidrequest.com:3000/' + + it('Returns empty array if no supported user syncs', function () { + let userSync = spec.getUserSyncs({ iframeEnabled: false, pixelEnabled: false }); + expect(userSync).to.be.an('array').with.lengthOf(0); + }); + + it('Returns valid iframe user sync', function () { + let userSync = spec.getUserSyncs({ iframeEnabled: true, pixelEnabled: false }); + expect(userSync).to.be.an('array').with.lengthOf(1); + expect(userSync[0].type).to.exist; + expect(userSync[0].url).to.exist; + expect(userSync[0].type).to.be.equal('iframe'); + expect(userSync[0].url).to.be.equal(USER_SYNC_URL_IFRAME); + }); + + it('Returns valid URL and type', function () { + let userSync = spec.getUserSyncs({ iframeEnabled: false, pixelEnabled: true }); + expect(userSync).to.be.an('array').with.lengthOf(1); + expect(userSync[0].type).to.exist; + expect(userSync[0].url).to.exist; + expect(userSync[0].type).to.be.equal('image'); + expect(userSync[0].url).to.be.equal(USER_SYNC_URL_IMAGE); + }); +}); From 0792373b4c878c63316f22ad72586fa7eed4649a Mon Sep 17 00:00:00 2001 From: Josh Date: Fri, 12 Mar 2021 07:32:41 -0800 Subject: [PATCH 2/8] Fulling working prebid using nativoBidAdapter. Support for GDPR and CCPA in user syncs. --- modules/nativoBidAdapter.js | 161 ++++++++++++++------- modules/nativoBidAdapter.md | 35 ++--- test/spec/modules/nativoBidAdapter_spec.js | 133 +++++++++++++---- 3 files changed, 219 insertions(+), 110 deletions(-) diff --git a/modules/nativoBidAdapter.js b/modules/nativoBidAdapter.js index bb78ff06d79..a196d2efd77 100644 --- a/modules/nativoBidAdapter.js +++ b/modules/nativoBidAdapter.js @@ -4,14 +4,14 @@ import { BANNER } from '../src/mediaTypes.js' // import { config } from 'src/config' const BIDDER_CODE = 'nativo' -const BIDDER_ENDPOINT = 'http://www.testlocalbidrequest.com:3000/requestBid/' // test local endpoint -const USER_SYNC_URL_IFRAME = 'http://www.testlocalbidrequest.com:3000/' -const USER_SYNC_URL_IMAGE = 'http://www.testlocalbidrequest.com:3000/' +const BIDDER_ENDPOINT = 'https://jadserve.postrelease.com/prebid' const TIME_TO_LIVE = 360 const SUPPORTED_AD_TYPES = [BANNER] +const bidRequestMap = {} + // Prebid adapter referrence doc: https://docs.prebid.org/dev-docs/bidder-adaptor.html export const spec = { @@ -26,7 +26,7 @@ export const spec = { * @return boolean True if this is a valid bid, and false otherwise. */ isBidRequestValid: function (bid) { - return !!(bid.adUnitCode) + return bid.params && !!bid.params.placementId }, /** @@ -38,16 +38,25 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: function (validBidRequests, bidderRequest) { - const payload = { - selector: validBidRequests[0].adUnitCode, - id: validBidRequests[0].bidId + const placementIds = [] + const placmentBidIdMap = {} + let placementId + validBidRequests.forEach((request) => { + placementId = request.params.placementId + placementIds.push(placementId) + placmentBidIdMap[placementId] = request.bidId + }) + bidRequestMap[bidderRequest.bidderRequestId] = placmentBidIdMap + + let params = { + ntv_ptd: placementIds.toString(), + ntv_pb_rid: bidderRequest.bidderRequestId, + ntv_url: encodeURIComponent(bidderRequest.refererInfo.referer), } - const payloadString = JSON.stringify(payload); let serverRequest = { - method: 'POST', - url: BIDDER_ENDPOINT, - data: payloadString + method: 'GET', + url: BIDDER_ENDPOINT + objectToQS(params), } return serverRequest @@ -67,18 +76,16 @@ export const spec = { if (!response || !response.body || utils.isEmpty(response.body)) return [] try { - // Parse the response and return a bidResponses array const body = response.body - // const headerValue = response.headers.get('some-response-header') const bidResponses = [] + const seatbids = body.seatbid - let seatbids = body.seatbid // Step through and grab pertinent data let bidResponse seatbids.forEach((seatbid) => { seatbid.bid.forEach((bid) => { bidResponse = { - requestId: body.id, + requestId: this.getRequestId(body.id, bid.impid), cpm: bid.price, currency: body.cur, width: bid.w, @@ -90,20 +97,6 @@ export const spec = { ad: bid.adm, meta: { advertiserDomains: bid.adomain, - // cat: bid.cat, - // impid: bid.impid, - // networkId: NETWORK_ID, - // networkName: NETWORK_NAME, - // agencyId: AGENCY_ID, - // agencyName: AGENCY_NAME, - // advertiserId: ADVERTISER_ID, - // advertiserName: ADVERTISER_NAME, - // advertiserDomains: [ARRAY_OF_ADVERTISER_DOMAINS], - // brandId: BRAND_ID, - // brandName: BRAND_NAME, - // primaryCatId: IAB_CATEGORY, - // secondaryCatIds: [ARRAY_OF_IAB_CATEGORIES], - // mediaType: MEDIA_TYPE }, } @@ -111,6 +104,9 @@ export const spec = { }) }) + // Don't need the map anymore as it was unique for one request/response + delete bidRequestMap[body.id] + return bidResponses } catch (error) { // If there is an error, return [] @@ -134,40 +130,60 @@ export const spec = { gdprConsent, uspConsent ) { - // console.log('syncOptions', syncOptions) // eslint-disable-line no-console - // console.log('serverResponses', serverResponses) // eslint-disable-line no-console - // console.log('gdprConsent', gdprConsent) // eslint-disable-line no-console - // console.log('uspConsent', uspConsent) // eslint-disable-line no-console - - const syncs = [] - + // Generate consent qs string let params = '' - // GDPR if (gdprConsent) { - params += '&gdpr=' + (gdprConsent.gdprApplies ? 1 : 0) - params += - '&gdpr_consent=' + encodeURIComponent(gdprConsent.consentString || '') + params = appendQSParamString( + params, + 'gdpr', + gdprConsent.gdprApplies ? 1 : 0 + ) + params = appendQSParamString( + params, + 'gdpr_consent', + encodeURIComponent(gdprConsent.consentString || '') + ) } - // CCPA if (uspConsent) { - params += '&us_privacy=' + encodeURIComponent(uspConsent) + params = appendQSParamString( + params, + 'us_privacy', + encodeURIComponent(uspConsent.uspConsent) + ) } - // TODO: We need to determine how we're including sync urls - if (syncOptions.iframeEnabled) { - syncs.push({ - type: 'iframe', - url: USER_SYNC_URL_IFRAME + params, - }) + // Get sync urls from the respnse and inject cinbsent params + const types = { + iframe: syncOptions.iframeEnabled, + image: syncOptions.pixelEnabled, } - if (syncOptions.pixelEnabled) { - syncs.push({ - type: 'image', - url: USER_SYNC_URL_IMAGE + params, + const syncs = [] + + let body + serverResponses.forEach((response) => { + if (!response) return + + body = response.body + + // Make sure we have valid content + if (!body && !body.seatbid && body.seatbid.length === 0) return + + body.seatbid.forEach((seatbid) => { + // Grab the syncs for each seatbid + seatbid.syncUrls.forEach((sync) => { + if (types[sync.type]) { + if (sync.url.trim() !== '') { + syncs.push({ + type: sync.type, + url: sync.url.replace('{GDPR_params}', params), + }) + } + } + }) }) - } + }) return syncs }, @@ -190,5 +206,44 @@ export const spec = { * @param {Object} bidder - The bid of which the targeting has been set */ onSetTargeting: function (bid) {}, + + /** + * Maps Prebid's bidId to Nativo's placementId values per unique bidderRequestId + * @param {String} bidderRequestId - The unique ID value associated with the bidderRequest + * @param {String} placementId - The placement ID value from Nativo + * @returns {String} - The bidId value associated with the corresponding placementId + */ + getRequestId: function (bidderRequestId, placementId) { + return ( + bidRequestMap[bidderRequestId] && + bidRequestMap[bidderRequestId][placementId] + ) + }, } registerBidder(spec) + +// Utils +/** + * Append QS param to existing string + * @param {String} str - String to append to + * @param {String} key - Key to append + * @param {String} value - Value to append + * @returns + */ +function appendQSParamString(str, key, value) { + return str + `${str.length ? '&' : ''}${key}=${value}` +} + +/** + * Convert an object to query string parameters + * @param {Object} obj - Object to convert + * @returns + */ +function objectToQS(obj) { + return ( + '?' + + Object.keys(obj).reduce((value, key) => { + return appendQSParamString(value, key, obj[key]) + }, '') + ) +} diff --git a/modules/nativoBidAdapter.md b/modules/nativoBidAdapter.md index 1ab7e1c7e3c..2c4f595d23f 100644 --- a/modules/nativoBidAdapter.md +++ b/modules/nativoBidAdapter.md @@ -3,7 +3,7 @@ ``` Module Name: Nativo Bid Adapter Module Type: Bidder Adapter -Maintainer: nativo@nativo.com +Maintainer: prebiddev@nativo.com ``` # Description @@ -19,35 +19,20 @@ gulp serve --modules=nativoBidAdapter ``` var adUnits = [ { - code: 'test-div', + code: 'div-gpt-ad-1460505748561-0', mediaTypes: { banner: { - sizes: [[300, 250]], // a display size + sizes: [[300, 250], [300,600]], } }, - bids: [ - { - bidder: "nativo", - params: { - placement: '12345' - } + // Replace this object to test a new Adapter! + bids: [{ + bidder: 'nativo', + params: { + placementId: 13144370 } - ] - },{ - code: 'test-div', - mediaTypes: { - banner: { - sizes: [[320, 50]], // a mobile size - } - }, - bids: [ - { - bidder: "nativo", - params: { - placement: 67890 - } - } - ] + }] + } ]; diff --git a/test/spec/modules/nativoBidAdapter_spec.js b/test/spec/modules/nativoBidAdapter_spec.js index 01919554151..e1132bf1b74 100644 --- a/test/spec/modules/nativoBidAdapter_spec.js +++ b/test/spec/modules/nativoBidAdapter_spec.js @@ -27,10 +27,10 @@ describe('nativoBidAdapterTests', function () { }) it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid) - delete bid.adUnitCode - bid.adUnitCode = 0 - expect(spec.isBidRequestValid(bid)).to.equal(false) + let bid2 = Object.assign({}, bid) + delete bid2.params + bid2.params = {} + expect(spec.isBidRequestValid(bid2)).to.equal(false) }) }) @@ -53,20 +53,27 @@ describe('nativoBidAdapterTests', function () { }, ] - it('should contain a valid selector', function () { - const request = spec.buildRequests(bidRequests) - const payload = JSON.parse(request.data) + it('url should contain query string parameters', function () { + const request = spec.buildRequests(bidRequests, { + bidderRequestId: 123456, + refererInfo: { + referer: 'https://www.test.com', + }, + }) + + expect(request.url).to.exist + expect(request.url).to.be.a('string') - expect(payload.selector).to.exist - expect(payload.selector).to.be.a('string') - expect(payload.selector).to.not.have.lengthOf(0) + expect(request.url).to.include('?') + expect(request.url).to.include('ntv_url') + expect(request.url).to.include('ntv_ptd') }) }) }) describe('interpretResponse', function () { let response = { - id: '1F254428-AB11-4D5E-9887-567B3F952CA5', + id: '126456', seatbid: [ { seat: 'seat_0', @@ -108,12 +115,19 @@ describe('interpretResponse', function () { ] let bidderRequest = { + id: 123456, bids: [ { - adUnitCode: 'code', + params: { + placementId: 1 + } }, ], } + + // mock + spec.getRequestId = () => 123456 + let result = spec.interpretResponse({ body: response }, { bidderRequest }) expect(Object.keys(result[0])).to.have.deep.members( Object.keys(expectedResponse[0]) @@ -130,29 +144,84 @@ describe('interpretResponse', function () { }) describe('getUserSyncs', function () { - const USER_SYNC_URL_IFRAME = 'http://www.testlocalbidrequest.com:3000/' - const USER_SYNC_URL_IMAGE = 'http://www.testlocalbidrequest.com:3000/' + const response = [ + { + body: { + cur: 'USD', + id: 'a136dbd8-4387-48bf-b8e4-ff9c1d6056ee', + seatbid: [ + { + bid: [{}], + seat: 'seat_0', + syncUrls: [ + { + type: 'image', + url: 'pixel-tracker-test-url/?{GDPR_params}', + }, + { + type: 'iframe', + url: 'iframe-tracker-test-url/?{GDPR_params}', + }, + ], + }, + ], + }, + }, + ] + + const gdprConsent = { + gdprApplies: true, + consentString: '111111' + } + + const uspConsent = { + uspConsent: '1YYY' + } it('Returns empty array if no supported user syncs', function () { - let userSync = spec.getUserSyncs({ iframeEnabled: false, pixelEnabled: false }); - expect(userSync).to.be.an('array').with.lengthOf(0); - }); + let userSync = spec.getUserSyncs( + { + iframeEnabled: false, + pixelEnabled: false, + }, + response, + gdprConsent, + uspConsent + ) + expect(userSync).to.be.an('array').with.lengthOf(0) + }) it('Returns valid iframe user sync', function () { - let userSync = spec.getUserSyncs({ iframeEnabled: true, pixelEnabled: false }); - expect(userSync).to.be.an('array').with.lengthOf(1); - expect(userSync[0].type).to.exist; - expect(userSync[0].url).to.exist; - expect(userSync[0].type).to.be.equal('iframe'); - expect(userSync[0].url).to.be.equal(USER_SYNC_URL_IFRAME); - }); + let userSync = spec.getUserSyncs( + { + iframeEnabled: true, + pixelEnabled: false, + }, + response, + gdprConsent, + uspConsent + ) + expect(userSync).to.be.an('array').with.lengthOf(1) + expect(userSync[0].type).to.exist + expect(userSync[0].url).to.exist + expect(userSync[0].type).to.be.equal('iframe') + expect(userSync[0].url).to.contain('gdpr=1&gdpr_consent=111111&us_privacy=1YYY') + }) it('Returns valid URL and type', function () { - let userSync = spec.getUserSyncs({ iframeEnabled: false, pixelEnabled: true }); - expect(userSync).to.be.an('array').with.lengthOf(1); - expect(userSync[0].type).to.exist; - expect(userSync[0].url).to.exist; - expect(userSync[0].type).to.be.equal('image'); - expect(userSync[0].url).to.be.equal(USER_SYNC_URL_IMAGE); - }); -}); + let userSync = spec.getUserSyncs( + { + iframeEnabled: false, + pixelEnabled: true, + }, + response, + gdprConsent, + uspConsent + ) + expect(userSync).to.be.an('array').with.lengthOf(1) + expect(userSync[0].type).to.exist + expect(userSync[0].url).to.exist + expect(userSync[0].type).to.be.equal('image') + expect(userSync[0].url).to.contain('gdpr=1&gdpr_consent=111111&us_privacy=1YYY') + }) +}) From 008e9068282b600d6aebd86baa3fca6374a75e49 Mon Sep 17 00:00:00 2001 From: Josh Date: Mon, 5 Apr 2021 10:27:38 -0700 Subject: [PATCH 3/8] Added defult size settings based on the largest ad unit. Added response body validation. Added consent to request url qs params. --- modules/nativoBidAdapter.js | 123 ++++++++++++++++++++++++++---------- 1 file changed, 89 insertions(+), 34 deletions(-) diff --git a/modules/nativoBidAdapter.js b/modules/nativoBidAdapter.js index a196d2efd77..99a570dee6b 100644 --- a/modules/nativoBidAdapter.js +++ b/modules/nativoBidAdapter.js @@ -1,10 +1,10 @@ -import * as utils from '../src/utils.js' -import { registerBidder } from '../src/adapters/bidderFactory.js' -import { BANNER } from '../src/mediaTypes.js' +import * as utils from "../src/utils.js" +import { registerBidder } from "../src/adapters/bidderFactory.js" +import { BANNER } from "../src/mediaTypes.js" // import { config } from 'src/config' -const BIDDER_CODE = 'nativo' -const BIDDER_ENDPOINT = 'https://jadserve.postrelease.com/prebid' +const BIDDER_CODE = "nativo" +const BIDDER_ENDPOINT = "https://jadserve.postrelease.com/prebid" const TIME_TO_LIVE = 360 @@ -16,7 +16,7 @@ const bidRequestMap = {} export const spec = { code: BIDDER_CODE, - aliases: ['ntv'], // short code + aliases: ["ntv"], // short code supportedMediaTypes: SUPPORTED_AD_TYPES, /** @@ -44,19 +44,38 @@ export const spec = { validBidRequests.forEach((request) => { placementId = request.params.placementId placementIds.push(placementId) - placmentBidIdMap[placementId] = request.bidId + placmentBidIdMap[placementId] = { + bidId: request.bidId, + size: getLargestSize(request.sizes), + } }) bidRequestMap[bidderRequest.bidderRequestId] = placmentBidIdMap - let params = { - ntv_ptd: placementIds.toString(), - ntv_pb_rid: bidderRequest.bidderRequestId, - ntv_url: encodeURIComponent(bidderRequest.refererInfo.referer), + let params = [ + { key: "ntv_ptd", value: placementIds.toString() }, + { key: "ntv_pb_rid", value: bidderRequest.bidderRequestId }, + { + key: "ntv_url", + value: encodeURIComponent(bidderRequest.refererInfo.referer), + }, + ] + + if (bidderRequest.gdprConsent) { + // Put on the beginning of the qs param array + params.unshift({ + key: "ntv_gdpr_consent", + value: bidderRequest.gdprConsent.consentString, + }) + } + + if (bidderRequest.uspConsent) { + // Put on the beginning of the qs param array + params.unshift({ key: "us_privacy", value: bidderRequest.uspConsent }) } let serverRequest = { - method: 'GET', - url: BIDDER_ENDPOINT + objectToQS(params), + method: "GET", + url: BIDDER_ENDPOINT + arrayToQS(params), } return serverRequest @@ -76,20 +95,25 @@ export const spec = { if (!response || !response.body || utils.isEmpty(response.body)) return [] try { - const body = response.body + const body = + typeof response.body === "string" + ? JSON.parse(response.body) + : response.body + const bidResponses = [] const seatbids = body.seatbid // Step through and grab pertinent data - let bidResponse + let bidResponse, adUnit seatbids.forEach((seatbid) => { seatbid.bid.forEach((bid) => { + adUnit = this.getRequestId(body.id, bid.impid) bidResponse = { - requestId: this.getRequestId(body.id, bid.impid), + requestId: adUnit.bidId, cpm: bid.price, currency: body.cur, - width: bid.w, - height: bid.h, + width: bid.w || adUnit.size[0], + height: bid.h || adUnit.size[1], creativeId: bid.crid, dealId: bid.id, netRevenue: true, @@ -131,25 +155,25 @@ export const spec = { uspConsent ) { // Generate consent qs string - let params = '' + let params = "" // GDPR if (gdprConsent) { params = appendQSParamString( params, - 'gdpr', + "gdpr", gdprConsent.gdprApplies ? 1 : 0 ) params = appendQSParamString( params, - 'gdpr_consent', - encodeURIComponent(gdprConsent.consentString || '') + "gdpr_consent", + encodeURIComponent(gdprConsent.consentString || "") ) } // CCPA if (uspConsent) { params = appendQSParamString( params, - 'us_privacy', + "us_privacy", encodeURIComponent(uspConsent.uspConsent) ) } @@ -163,21 +187,27 @@ export const spec = { let body serverResponses.forEach((response) => { - if (!response) return + // If the bid response was empty, return [] + if (!response || !response.body || utils.isEmpty(response.body)) { + return syncs + } - body = response.body + body = + typeof response.body === "string" + ? JSON.parse(response.body) + : response.body // Make sure we have valid content - if (!body && !body.seatbid && body.seatbid.length === 0) return + if (!body || !body.seatbid || body.seatbid.length === 0) return body.seatbid.forEach((seatbid) => { // Grab the syncs for each seatbid seatbid.syncUrls.forEach((sync) => { if (types[sync.type]) { - if (sync.url.trim() !== '') { + if (sync.url.trim() !== "") { syncs.push({ type: sync.type, - url: sync.url.replace('{GDPR_params}', params), + url: sync.url.replace("{GDPR_params}", params), }) } } @@ -231,7 +261,7 @@ registerBidder(spec) * @returns */ function appendQSParamString(str, key, value) { - return str + `${str.length ? '&' : ''}${key}=${value}` + return str + `${str.length ? "&" : ""}${key}=${value}` } /** @@ -239,11 +269,36 @@ function appendQSParamString(str, key, value) { * @param {Object} obj - Object to convert * @returns */ -function objectToQS(obj) { +function arrayToQS(arr) { return ( - '?' + - Object.keys(obj).reduce((value, key) => { - return appendQSParamString(value, key, obj[key]) - }, '') + "?" + + arr.reduce((value, obj) => { + return appendQSParamString(value, obj.key, obj.value) + }, "") ) } + +/** + * Get the largest size array + * @param {Array} sizes - Array of size arrays + * @returns Size array with the largest area + */ +function getLargestSize(sizes, method = area) { + if (!sizes || sizes.length === 0) return [] + if (sizes.length === 1) return sizes[0] + + return sizes.reduce((prev, current) => { + if (method(current) > method(prev)) { + return current + } else { + return prev + } + }) +} + +/** + * Calculate the area + * @param {Array} size - [width, height] + * @returns The calculated area + */ +const area = (size) => size[0] * size[1] From 21f3e0c7c241e2751a61a92d130f3d8d1caa448c Mon Sep 17 00:00:00 2001 From: Josh Date: Wed, 7 Apr 2021 10:13:25 -0700 Subject: [PATCH 4/8] Changed bidder endpoint url --- modules/nativoBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/nativoBidAdapter.js b/modules/nativoBidAdapter.js index 99a570dee6b..4feee0d1fdb 100644 --- a/modules/nativoBidAdapter.js +++ b/modules/nativoBidAdapter.js @@ -4,7 +4,7 @@ import { BANNER } from "../src/mediaTypes.js" // import { config } from 'src/config' const BIDDER_CODE = "nativo" -const BIDDER_ENDPOINT = "https://jadserve.postrelease.com/prebid" +const BIDDER_ENDPOINT = "https://exchange.postrelease.com/prebid" const TIME_TO_LIVE = 360 From 1a33f2cac9e7c44a6b4df02b22c4729231e01827 Mon Sep 17 00:00:00 2001 From: Josh Date: Wed, 7 Apr 2021 10:34:10 -0700 Subject: [PATCH 5/8] Changed double quotes to single quotes. --- modules/nativoBidAdapter.js | 48 ++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/modules/nativoBidAdapter.js b/modules/nativoBidAdapter.js index 4feee0d1fdb..94a93667ca9 100644 --- a/modules/nativoBidAdapter.js +++ b/modules/nativoBidAdapter.js @@ -1,10 +1,10 @@ -import * as utils from "../src/utils.js" -import { registerBidder } from "../src/adapters/bidderFactory.js" -import { BANNER } from "../src/mediaTypes.js" +import * as utils from '../src/utils.js' +import { registerBidder } from '../src/adapters/bidderFactory.js' +import { BANNER } from '../src/mediaTypes.js' // import { config } from 'src/config' -const BIDDER_CODE = "nativo" -const BIDDER_ENDPOINT = "https://exchange.postrelease.com/prebid" +const BIDDER_CODE = 'nativo' +const BIDDER_ENDPOINT = 'https://exchange.postrelease.com/prebid' const TIME_TO_LIVE = 360 @@ -16,7 +16,7 @@ const bidRequestMap = {} export const spec = { code: BIDDER_CODE, - aliases: ["ntv"], // short code + aliases: ['ntv'], // short code supportedMediaTypes: SUPPORTED_AD_TYPES, /** @@ -52,10 +52,10 @@ export const spec = { bidRequestMap[bidderRequest.bidderRequestId] = placmentBidIdMap let params = [ - { key: "ntv_ptd", value: placementIds.toString() }, - { key: "ntv_pb_rid", value: bidderRequest.bidderRequestId }, + { key: 'ntv_ptd', value: placementIds.toString() }, + { key: 'ntv_pb_rid', value: bidderRequest.bidderRequestId }, { - key: "ntv_url", + key: 'ntv_url', value: encodeURIComponent(bidderRequest.refererInfo.referer), }, ] @@ -63,18 +63,18 @@ export const spec = { if (bidderRequest.gdprConsent) { // Put on the beginning of the qs param array params.unshift({ - key: "ntv_gdpr_consent", + key: 'ntv_gdpr_consent', value: bidderRequest.gdprConsent.consentString, }) } if (bidderRequest.uspConsent) { // Put on the beginning of the qs param array - params.unshift({ key: "us_privacy", value: bidderRequest.uspConsent }) + params.unshift({ key: 'us_privacy', value: bidderRequest.uspConsent }) } let serverRequest = { - method: "GET", + method: 'GET', url: BIDDER_ENDPOINT + arrayToQS(params), } @@ -96,7 +96,7 @@ export const spec = { try { const body = - typeof response.body === "string" + typeof response.body === 'string' ? JSON.parse(response.body) : response.body @@ -155,25 +155,25 @@ export const spec = { uspConsent ) { // Generate consent qs string - let params = "" + let params = '' // GDPR if (gdprConsent) { params = appendQSParamString( params, - "gdpr", + 'gdpr', gdprConsent.gdprApplies ? 1 : 0 ) params = appendQSParamString( params, - "gdpr_consent", - encodeURIComponent(gdprConsent.consentString || "") + 'gdpr_consent', + encodeURIComponent(gdprConsent.consentString || '') ) } // CCPA if (uspConsent) { params = appendQSParamString( params, - "us_privacy", + 'us_privacy', encodeURIComponent(uspConsent.uspConsent) ) } @@ -193,7 +193,7 @@ export const spec = { } body = - typeof response.body === "string" + typeof response.body === 'string' ? JSON.parse(response.body) : response.body @@ -204,10 +204,10 @@ export const spec = { // Grab the syncs for each seatbid seatbid.syncUrls.forEach((sync) => { if (types[sync.type]) { - if (sync.url.trim() !== "") { + if (sync.url.trim() !== '') { syncs.push({ type: sync.type, - url: sync.url.replace("{GDPR_params}", params), + url: sync.url.replace('{GDPR_params}', params), }) } } @@ -261,7 +261,7 @@ registerBidder(spec) * @returns */ function appendQSParamString(str, key, value) { - return str + `${str.length ? "&" : ""}${key}=${value}` + return str + `${str.length ? '&' : ''}${key}=${value}` } /** @@ -271,10 +271,10 @@ function appendQSParamString(str, key, value) { */ function arrayToQS(arr) { return ( - "?" + + '?' + arr.reduce((value, obj) => { return appendQSParamString(value, obj.key, obj.value) - }, "") + }, '') ) } From 517cd5b2f20bc31ec75b68df76a2047923bcf3e1 Mon Sep 17 00:00:00 2001 From: Josh Date: Thu, 8 Apr 2021 07:34:10 -0700 Subject: [PATCH 6/8] Reverted package-json.lock to remove modifications from PR --- package-lock.json | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3fe857a89e8..330298cbaac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.27.0-pre", + "version": "4.15.0-pre", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -22437,6 +22437,7 @@ "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", "dev": true, + "optional": true, "requires": { "arr-flatten": "^1.1.0", "array-unique": "^0.3.2", @@ -22455,6 +22456,7 @@ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, + "optional": true, "requires": { "is-extendable": "^0.1.0" } @@ -22487,6 +22489,7 @@ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", "dev": true, + "optional": true, "requires": { "extend-shallow": "^2.0.1", "is-number": "^3.0.0", @@ -22499,6 +22502,7 @@ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, + "optional": true, "requires": { "is-extendable": "^0.1.0" } @@ -22553,13 +22557,15 @@ "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true + "dev": true, + "optional": true }, "is-number": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, + "optional": true, "requires": { "kind-of": "^3.0.2" }, @@ -22569,6 +22575,7 @@ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, + "optional": true, "requires": { "is-buffer": "^1.1.5" } @@ -22580,6 +22587,7 @@ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", "dev": true, + "optional": true, "requires": { "arr-diff": "^4.0.0", "array-unique": "^0.3.2", @@ -22629,6 +22637,7 @@ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", "dev": true, + "optional": true, "requires": { "is-number": "^3.0.0", "repeat-string": "^1.6.1" From 44bf4286f482af11c94e03f8d238409ee9b14310 Mon Sep 17 00:00:00 2001 From: Josh Date: Thu, 15 Apr 2021 14:14:05 -0700 Subject: [PATCH 7/8] Added optional bidder param 'url' so the ad server can force- match an existing placement --- modules/nativoBidAdapter.js | 7 +++++-- modules/nativoBidAdapter.md | 3 ++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/modules/nativoBidAdapter.js b/modules/nativoBidAdapter.js index 94a93667ca9..aea875b443d 100644 --- a/modules/nativoBidAdapter.js +++ b/modules/nativoBidAdapter.js @@ -40,8 +40,9 @@ export const spec = { buildRequests: function (validBidRequests, bidderRequest) { const placementIds = [] const placmentBidIdMap = {} - let placementId + let placementId, pageUrl validBidRequests.forEach((request) => { + pageUrl = pageUrl || request.params.url // Use the first url value found placementId = request.params.placementId placementIds.push(placementId) placmentBidIdMap[placementId] = { @@ -51,12 +52,14 @@ export const spec = { }) bidRequestMap[bidderRequest.bidderRequestId] = placmentBidIdMap + if(!pageUrl) pageUrl = bidderRequest.refererInfo.referer + let params = [ { key: 'ntv_ptd', value: placementIds.toString() }, { key: 'ntv_pb_rid', value: bidderRequest.bidderRequestId }, { key: 'ntv_url', - value: encodeURIComponent(bidderRequest.refererInfo.referer), + value: encodeURIComponent(pageUrl), }, ] diff --git a/modules/nativoBidAdapter.md b/modules/nativoBidAdapter.md index 2c4f595d23f..ec0980aae50 100644 --- a/modules/nativoBidAdapter.md +++ b/modules/nativoBidAdapter.md @@ -29,7 +29,8 @@ var adUnits = [ bids: [{ bidder: 'nativo', params: { - placementId: 13144370 + placementId: 1125609, + url: 'https://test-sites.internal.nativo.net/testing/prebid_adpater.html' } }] From bfe0e1e60167927003cb693814547dd01de5efcc Mon Sep 17 00:00:00 2001 From: Josh Date: Thu, 15 Apr 2021 14:19:27 -0700 Subject: [PATCH 8/8] Lint fix. Added space after if. --- modules/nativoBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/nativoBidAdapter.js b/modules/nativoBidAdapter.js index aea875b443d..d396bd4d495 100644 --- a/modules/nativoBidAdapter.js +++ b/modules/nativoBidAdapter.js @@ -52,7 +52,7 @@ export const spec = { }) bidRequestMap[bidderRequest.bidderRequestId] = placmentBidIdMap - if(!pageUrl) pageUrl = bidderRequest.refererInfo.referer + if (!pageUrl) pageUrl = bidderRequest.refererInfo.referer let params = [ { key: 'ntv_ptd', value: placementIds.toString() },