From 55cf493721a19e55b50f0dc93f785da482036550 Mon Sep 17 00:00:00 2001 From: John Rosendahl Date: Wed, 14 Nov 2018 17:54:28 -0700 Subject: [PATCH] Updated Sovrn Bid Adaptor for MultiSized and added Error Call Home. (#3237) * Updated Bid Adaptor for MultiSized and added Error Call Home * Updated to conform with prebid requirements * added missing this * Added tests for more coverage * simplified error object for edge 15 --- modules/sovrnBidAdapter.js | 216 ++++++++++++++-------- test/spec/modules/sovrnBidAdapter_spec.js | 140 ++++++++++++-- 2 files changed, 266 insertions(+), 90 deletions(-) diff --git a/modules/sovrnBidAdapter.js b/modules/sovrnBidAdapter.js index 4f1eb298794..7ebe5dd0ecc 100644 --- a/modules/sovrnBidAdapter.js +++ b/modules/sovrnBidAdapter.js @@ -1,7 +1,9 @@ -import * as utils from 'src/utils'; -import { registerBidder } from 'src/adapters/bidderFactory'; -import { BANNER } from 'src/mediaTypes'; -import { REPO_AND_VERSION } from 'src/constants'; +import * as utils from 'src/utils' +import { registerBidder } from 'src/adapters/bidderFactory' +import { BANNER } from 'src/mediaTypes' +import { REPO_AND_VERSION } from 'src/constants' +const errorUrl = 'https://pcb.aws.lijit.com/c' +let errorpxls = [] export const spec = { code: 'sovrn', @@ -22,48 +24,59 @@ export const spec = { * @return object of parameters for Prebid AJAX request */ buildRequests: function(bidReqs, bidderRequest) { - const loc = utils.getTopWindowLocation(); - let sovrnImps = []; - let iv; - utils._each(bidReqs, function (bid) { - iv = iv || utils.getBidIdParameter('iv', bid.params); - sovrnImps.push({ - id: bid.bidId, - banner: { w: 1, h: 1 }, - tagid: String(utils.getBidIdParameter('tagid', bid.params)), - bidfloor: utils.getBidIdParameter('bidfloor', bid.params) + try { + const loc = utils.getTopWindowLocation(); + let sovrnImps = []; + let iv; + utils._each(bidReqs, function (bid) { + iv = iv || utils.getBidIdParameter('iv', bid.params); + bid.sizes = ((utils.isArray(bid.sizes) && utils.isArray(bid.sizes[0])) ? bid.sizes : [bid.sizes]) + bid.sizes = bid.sizes.filter(size => utils.isArray(size)) + const processedSizes = bid.sizes.map(size => ({w: parseInt(size[0], 10), h: parseInt(size[1], 10)})) + sovrnImps.push({ + id: bid.bidId, + banner: { + format: processedSizes, + w: 1, + h: 1, + }, + tagid: String(utils.getBidIdParameter('tagid', bid.params)), + bidfloor: utils.getBidIdParameter('bidfloor', bid.params) + }); }); - }); - const sovrnBidReq = { - id: utils.getUniqueIdentifierStr(), - imp: sovrnImps, - site: { - domain: loc.host, - page: loc.host + loc.pathname + loc.search + loc.hash - } - }; + const sovrnBidReq = { + id: utils.getUniqueIdentifierStr(), + imp: sovrnImps, + site: { + domain: loc.host, + page: loc.host + loc.pathname + loc.search + loc.hash + } + }; - if (bidderRequest && bidderRequest.gdprConsent) { - sovrnBidReq.regs = { - ext: { - gdpr: +bidderRequest.gdprConsent.gdprApplies - }}; - sovrnBidReq.user = { - ext: { - consent: bidderRequest.gdprConsent.consentString - }}; - } + if (bidderRequest && bidderRequest.gdprConsent) { + sovrnBidReq.regs = { + ext: { + gdpr: +bidderRequest.gdprConsent.gdprApplies + }}; + sovrnBidReq.user = { + ext: { + consent: bidderRequest.gdprConsent.consentString + }}; + } - let url = `//ap.lijit.com/rtb/bid?` + - `src=${REPO_AND_VERSION}`; - if (iv) url += `&iv=${iv}`; + let url = `//ap.lijit.com/rtb/bid?` + + `src=${REPO_AND_VERSION}`; + if (iv) url += `&iv=${iv}`; - return { - method: 'POST', - url: url, - data: JSON.stringify(sovrnBidReq), - options: {contentType: 'text/plain'} - }; + return { + method: 'POST', + url: url, + data: JSON.stringify(sovrnBidReq), + options: {contentType: 'text/plain'} + } + } catch (e) { + new LogError(e, {bidReqs, bidderRequest}).append() + } }, /** @@ -72,48 +85,99 @@ export const spec = { * @return {Bid[]} An array of formatted bids. */ interpretResponse: function({ body: {id, seatbid} }) { - let sovrnBidResponses = []; - if (id && - seatbid && - seatbid.length > 0 && - seatbid[0].bid && - seatbid[0].bid.length > 0) { - seatbid[0].bid.map(sovrnBid => { - sovrnBidResponses.push({ - requestId: sovrnBid.impid, - cpm: parseFloat(sovrnBid.price), - width: parseInt(sovrnBid.w), - height: parseInt(sovrnBid.h), - creativeId: sovrnBid.crid || sovrnBid.id, - dealId: sovrnBid.dealid || null, - currency: 'USD', - netRevenue: true, - mediaType: BANNER, - ad: decodeURIComponent(`${sovrnBid.adm}`), - ttl: 60 + try { + let sovrnBidResponses = []; + if (id && + seatbid && + seatbid.length > 0 && + seatbid[0].bid && + seatbid[0].bid.length > 0) { + seatbid[0].bid.map(sovrnBid => { + sovrnBidResponses.push({ + requestId: sovrnBid.impid, + cpm: parseFloat(sovrnBid.price), + width: parseInt(sovrnBid.w), + height: parseInt(sovrnBid.h), + creativeId: sovrnBid.crid || sovrnBid.id, + dealId: sovrnBid.dealid || null, + currency: 'USD', + netRevenue: true, + mediaType: BANNER, + ad: decodeURIComponent(`${sovrnBid.adm}`), + ttl: 60 + }); }); - }); + } + return sovrnBidResponses + } catch (e) { + new LogError(e, {id, seatbid}).append() } - return sovrnBidResponses; }, getUserSyncs: function(syncOptions, serverResponses, gdprConsent) { - if (serverResponses && serverResponses.length !== 0 && syncOptions.iframeEnabled) { - let iidArr = serverResponses.filter(rsp => rsp.body && rsp.body.ext && rsp.body.ext.iid) - .map(rsp => { return rsp.body.ext.iid }); - let consentString = ''; - if (gdprConsent && gdprConsent.gdprApplies && typeof gdprConsent.consentString === 'string') { - consentString = gdprConsent.consentString + try { + let tracks = [] + if (serverResponses && serverResponses.length !== 0 && syncOptions.iframeEnabled) { + let iidArr = serverResponses.filter(rsp => rsp.body && rsp.body.ext && rsp.body.ext.iid) + .map(rsp => { return rsp.body.ext.iid }); + let consentString = ''; + if (gdprConsent && gdprConsent.gdprApplies && typeof gdprConsent.consentString === 'string') { + consentString = gdprConsent.consentString + } + if (iidArr[0]) { + tracks.push({ + type: 'iframe', + url: '//ap.lijit.com/beacon?informer=' + iidArr[0] + '&gdpr_consent=' + consentString, + }); + } + } + if (errorpxls.length && syncOptions.pixelEnabled) { + tracks = tracks.concat(errorpxls) } - if (iidArr[0]) { - return [{ - type: 'iframe', - url: '//ap.lijit.com/beacon?informer=' + iidArr[0] + '&gdpr_consent=' + consentString, - }]; + return tracks + } catch (e) { + if (syncOptions.pixelEnabled) { + return errorpxls } + return [] } - return []; }, -}; +} + +export class LogError { + constructor(e, data) { + utils.logError(e) + this.error = {} + this.error.t = utils.timestamp() + this.error.m = e.message + this.error.s = e.stack + this.error.d = data + this.error.v = REPO_AND_VERSION + this.error.u = utils.getTopWindowLocation().href + this.error.ua = navigator.userAgent + } + buildErrorString(obj) { + return errorUrl + '?b=' + btoa(JSON.stringify(obj)) + } + append() { + let errstr = this.buildErrorString(this.error) + if (errstr.length > 2083) { + delete this.error.d + errstr = this.buildErrorString(this.error) + if (errstr.length > 2083) { + delete this.error.s + errstr = this.buildErrorString(this.error) + if (errstr.length > 2083) { + errstr = this.buildErrorString({m: 'unknown error message', t: this.error.t, u: this.error.u}) + } + } + } + let obj = {type: 'image', url: errstr} + errorpxls.push(obj) + } + static getErrPxls() { + return errorpxls + } +} registerBidder(spec); diff --git a/test/spec/modules/sovrnBidAdapter_spec.js b/test/spec/modules/sovrnBidAdapter_spec.js index 22c93505ecf..a774aa64062 100644 --- a/test/spec/modules/sovrnBidAdapter_spec.js +++ b/test/spec/modules/sovrnBidAdapter_spec.js @@ -1,7 +1,8 @@ import { expect } from 'chai'; -import { spec } from 'modules/sovrnBidAdapter'; +import { spec, LogError } from 'modules/sovrnBidAdapter'; import { newBidder } from 'src/adapters/bidderFactory'; import { REPO_AND_VERSION } from 'src/constants'; +import { SSL_OP_SINGLE_ECDH_USE } from 'constants'; const ENDPOINT = `//ap.lijit.com/rtb/bid?src=${REPO_AND_VERSION}`; @@ -16,7 +17,8 @@ describe('sovrnBidAdapter', function() { }, 'adUnitCode': 'adunit-code', 'sizes': [ - [300, 250] + [300, 250], + [300, 600] ], 'bidId': '30b31c1838de1e', 'bidderRequestId': '22edbae2733bf6', @@ -47,7 +49,8 @@ describe('sovrnBidAdapter', function() { }, 'adUnitCode': 'adunit-code', 'sizes': [ - [300, 250] + [300, 250], + [300, 600] ], 'bidId': '30b31c1838de1e', 'bidderRequestId': '22edbae2733bf6', @@ -64,6 +67,33 @@ describe('sovrnBidAdapter', function() { expect(request.url).to.equal(ENDPOINT) }); + it('sets the proper banner object', function() { + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}, {w: 300, h: 600}]) + expect(payload.imp[0].banner.w).to.equal(1) + expect(payload.imp[0].banner.h).to.equal(1) + }) + + it('accepts a single array as a size', function() { + const singleSize = [{ + 'bidder': 'sovrn', + 'params': { + 'tagid': '403370', + 'iv': 'vet' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [300, 250], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475' + }]; + const request = spec.buildRequests(singleSize) + const payload = JSON.parse(request.data) + expect(payload.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}]) + expect(payload.imp[0].banner.w).to.equal(1) + expect(payload.imp[0].banner.h).to.equal(1) + }) + it('sends \'iv\' as query param if present', function () { const ivBidRequests = [{ 'bidder': 'sovrn', @@ -73,7 +103,8 @@ describe('sovrnBidAdapter', function() { }, 'adUnitCode': 'adunit-code', 'sizes': [ - [300, 250] + [300, 250], + [300, 600] ], 'bidId': '30b31c1838de1e', 'bidderRequestId': '22edbae2733bf6', @@ -116,7 +147,8 @@ describe('sovrnBidAdapter', function() { }, 'adUnitCode': 'adunit-code', 'sizes': [ - [300, 250] + [300, 250], + [300, 600] ], 'bidId': '30b31c1838de1e', 'bidderRequestId': '22edbae2733bf6', @@ -268,19 +300,99 @@ describe('sovrnBidAdapter', function() { 'type': 'iframe', 'url': '//ap.lijit.com/beacon?informer=13487408&gdpr_consent=', } - ]; + ] let returnStatement = spec.getUserSyncs(syncOptions, serverResponse); expect(returnStatement[0]).to.deep.equal(expectedReturnStatement[0]); - }); + }) it('should not return if iid missing on server response', () => { - let returnStatement = spec.getUserSyncs(syncOptions, []); + let returnStatement = spec.getUserSyncs(syncOptions, []) expect(returnStatement).to.be.empty; - }); + }) it('should not return if iframe syncs disabled', () => { - let returnStatement = spec.getUserSyncs(iframeDisabledSyncOptions, serverResponse); - expect(returnStatement).to.be.empty; - }); - }); -}); + let returnStatement = spec.getUserSyncs(iframeDisabledSyncOptions, serverResponse) + expect(returnStatement).to.be.empty + }) + }) + describe('LogError', () => { + it('should build and append an error object', () => { + const thrown = { + message: 'message', + stack: 'stack' + } + const data = {name: 'Oscar Hathenswiotch'} + const err = new LogError(thrown, data) + err.append() + const errList = LogError.getErrPxls() + expect(errList.length).to.equal(1) + const errdata = JSON.parse(atob(errList[0].url.split('=')[1])) + expect(errdata.d.name).to.equal('Oscar Hathenswiotch') + }) + it('should drop data when there is too much', () => { + const thrown = { + message: 'message', + stack: 'stack' + } + const tooLong = () => { + let str = '' + for (let i = 0; i < 10000; i++) { + str = str + String.fromCharCode(i % 100) + } + return str + } + const data = {name: 'Oscar Hathenswiotch', tooLong: tooLong()} + const err = new LogError(thrown, data) + err.append() + const errList = LogError.getErrPxls() + expect(errList.length).to.equal(2) + const errdata = JSON.parse(atob(errList[1].url.split('=')[1])) + expect(errdata.d).to.be.an('undefined') + }) + it('should drop data and stack when there is too much', () => { + const thrown = { + message: 'message', + stack: 'stack' + } + const tooLong = () => { + let str = '' + for (let i = 0; i < 10000; i++) { + str = str + String.fromCharCode(i % 100) + } + return str + } + const data = {name: 'Oscar Hathenswiotch'} + thrown.stack = tooLong() + const err = new LogError(thrown, data) + err.append() + const errList = LogError.getErrPxls() + expect(errList.length).to.equal(3) + const errdata = JSON.parse(atob(errList[2].url.split('=')[1])) + expect(errdata.d).to.be.an('undefined') + expect(errdata.s).to.be.an('undefined') + }) + it('should drop send a reduced message when other reduction methods fail', () => { + const thrown = { + message: 'message', + stack: 'stack' + } + const tooLong = () => { + let str = '' + for (let i = 0; i < 10000; i++) { + str = str + String.fromCharCode(i % 100) + } + return str + } + const data = {name: 'Oscar Hathenswiotch'} + thrown.message = tooLong() + const err = new LogError(thrown, data) + err.append() + const errList = LogError.getErrPxls() + expect(errList.length).to.equal(4) + const errdata = JSON.parse(atob(errList[3].url.split('=')[1])) + expect(errdata.d).to.be.an('undefined') + expect(errdata.s).to.be.an('undefined') + expect(errdata.m).to.equal('unknown error message') + }) + }) +})