From 5a2d58c18a6873bb71869d3242b1bd62772531c2 Mon Sep 17 00:00:00 2001 From: chaac Date: Thu, 22 Dec 2016 13:18:52 -0800 Subject: [PATCH 01/27] GumGum adapter - include new parameter on the request (#882) * GumGum adapter - include new parameter on the request * GumGum adapter - include cookie flag --- src/adapters/gumgum.js | 29 +++++++++++++++++++---------- test/spec/adapters/gumgum_spec.js | 15 ++++++++++++--- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/src/adapters/gumgum.js b/src/adapters/gumgum.js index 7f53358e3e3..551a49042f4 100644 --- a/src/adapters/gumgum.js +++ b/src/adapters/gumgum.js @@ -10,12 +10,13 @@ const GumgumAdapter = function GumgumAdapter() { const bidEndpoint = `https://g2.gumgum.com/hbid/imp`; - let WINDOW; - let SCREEN; + let topWindow; + let topScreen; + let pageViewId; try { - WINDOW = global.top; - SCREEN = WINDOW.screen; + topWindow = global.top; + topScreen = topWindow.screen; } catch (error) { utils.logError(error); return; @@ -23,12 +24,13 @@ const GumgumAdapter = function GumgumAdapter() { function _callBids({ bids }) { const browserParams = { - vw: WINDOW.innerWidth, - vh: WINDOW.innerHeight, - sw: SCREEN.width, - sh: SCREEN.height, - pu: WINDOW.location.href, - dpr: WINDOW.devicePixelRatio || 1 + vw: topWindow.innerWidth, + vh: topWindow.innerHeight, + sw: topScreen.width, + sh: topScreen.height, + pu: topWindow.location.href, + ce: navigator.cookieEnabled, + dpr: topWindow.devicePixelRatio || 1 }; utils._each(bids, bidRequest => { const { bidId @@ -58,6 +60,9 @@ const GumgumAdapter = function GumgumAdapter() { /* slot ads require a slot id */ if (slotId) bid.si = slotId; + /* include the pageViewId, if any */ + if (pageViewId) bid.pv = pageViewId; + const cachedBid = Object.assign({ placementCode, id: bidId @@ -73,6 +78,10 @@ const GumgumAdapter = function GumgumAdapter() { const _handleGumGumResponse = cachedBidRequest => bidResponse => { const ad = bidResponse && bidResponse.ad; + const pag = bidResponse && bidResponse.pag; + /* cache the pageViewId */ + if (pag && pag.pvid) pageViewId = pag.pvid; + /* create the bid */ if (ad && ad.id) { const bid = bidfactory.createBid(1); const { t: trackingId diff --git a/test/spec/adapters/gumgum_spec.js b/test/spec/adapters/gumgum_spec.js index 1c4f8a329f7..fe48ae3a3f4 100644 --- a/test/spec/adapters/gumgum_spec.js +++ b/test/spec/adapters/gumgum_spec.js @@ -57,6 +57,9 @@ describe('gumgum adapter', () => { sizes: [ [728, 90] ] }] }; + const pageParams = { + "pvid": "PVID" + }; const bidderResponse = { "ad": { "id": 1, @@ -67,8 +70,13 @@ describe('gumgum adapter', () => { "du": "http://example.com/", "price": TEST.CPM, "impurl": "http://example.com/" - } + }, + "pag": pageParams }; + const emptyResponse = { + "ad": {}, + "pag": pageParams + } function mockBidResponse(response) { sandbox.stub(bidManager, 'addBidResponse'); @@ -153,7 +161,8 @@ describe('gumgum adapter', () => { }); it('should have a GOOD status code', () => { - expect(successfulBid.getStatusCode()).to.eql(STATUS.GOOD); + const STATUS_CODE = successfulBid.getStatusCode(); + expect(STATUS_CODE).to.eql(STATUS.GOOD); }); it('should use the CPM returned by the server', () => { @@ -176,7 +185,7 @@ describe('gumgum adapter', () => { let noBid; beforeEach(() => { - noBid = mockBidResponse({}); + noBid = mockBidResponse(emptyResponse); }); it('should add one bid', () => { From 4ec984713986ab1d33d97961db09d7ac1b6d07c1 Mon Sep 17 00:00:00 2001 From: nissSK Date: Mon, 26 Dec 2016 21:16:08 +0200 Subject: [PATCH 02/27] New Sekindo Universal Mccann (UM) Adapter (#801) * New Sekindo Universal Mccann (UM) Adapter * utils.getBidIdParameter name change, add testing file * apces instead of tabs * getBidIdParamater change name * add SekindoUM * load script instead of doc write it to iframe * bug fix * remove unused imports in the test * remove unused imports in the test * restoring import {expect} from 'chai' --- adapters.json | 1 + integrationExamples/gpt/pbjs_example_gpt.html | 13 ++- src/adapters/sekindoUM.js | 99 +++++++++++++++++++ test/spec/adapters/sekindoUM_spec.js | 86 ++++++++++++++++ 4 files changed, 192 insertions(+), 7 deletions(-) create mode 100755 src/adapters/sekindoUM.js create mode 100644 test/spec/adapters/sekindoUM_spec.js diff --git a/adapters.json b/adapters.json index ebfa776dfa8..6242bd55c1f 100644 --- a/adapters.json +++ b/adapters.json @@ -24,6 +24,7 @@ "pulsepoint", "rhythmone", "rubicon", + "sekindoUM", "sonobi", "sovrn", "springserve", diff --git a/integrationExamples/gpt/pbjs_example_gpt.html b/integrationExamples/gpt/pbjs_example_gpt.html index 9f576fe0ed1..06a4afa5d47 100644 --- a/integrationExamples/gpt/pbjs_example_gpt.html +++ b/integrationExamples/gpt/pbjs_example_gpt.html @@ -192,13 +192,12 @@ impid: 36 } }, - { - bidder: 'sekindo', - params: { - spaceId: 14071, // REQUIRED int. To get one, contact http://www.sekindo.com - bidfloor: 0.2 // OPTIONAL float bid floor in $ CPM - } - }, + { + bidder: 'sekindoUM', + params: { + spaceId: 14071 // REQUIRED int. To get one, contact http://www.sekindo.com + } + }, { bidder: 'memeglobal', params: { diff --git a/src/adapters/sekindoUM.js b/src/adapters/sekindoUM.js new file mode 100755 index 00000000000..f6c3d423742 --- /dev/null +++ b/src/adapters/sekindoUM.js @@ -0,0 +1,99 @@ +import { getBidRequest } from '../utils.js'; +var CONSTANTS = require('../constants.json'); +var utils = require('../utils.js'); +var bidfactory = require('../bidfactory.js'); +var bidmanager = require('../bidmanager.js'); +var adloader = require('../adloader.js'); + +var sekindoUMAdapter; +sekindoUMAdapter = function sekindoUMAdapter() { + + function _callBids(params) { + var bids = params.bids; + var bidsCount = bids.length; + + var pubUrl = null; + if (parent !== window) + pubUrl = document.referrer; + else + pubUrl = window.location.href; + + for (var i = 0; i < bidsCount; i++) { + var bidReqeust = bids[i]; + var callbackId = bidReqeust.bidId; + _requestBids(bidReqeust, callbackId, pubUrl); + //store a reference to the bidRequest from the callback id + //bidmanager.pbCallbackMap[callbackId] = bidReqeust; + } + } + + $$PREBID_GLOBAL$$.sekindoCB = function(callbackId, response) { + var bidObj = getBidRequest(callbackId); + if (typeof (response) !== 'undefined' && typeof (response.cpm) !== 'undefined') { + var bid = []; + if (bidObj) { + var bidCode = bidObj.bidder; + var placementCode = bidObj.placementCode; + + if (response.cpm !== undefined && response.cpm > 0) { + + bid = bidfactory.createBid(CONSTANTS.STATUS.GOOD); + bid.callback_uid = callbackId; + bid.bidderCode = bidCode; + bid.creative_id = response.adId; + bid.cpm = parseFloat(response.cpm); + bid.ad = response.ad; + bid.width = response.width; + bid.height = response.height; + + bidmanager.addBidResponse(placementCode, bid); + } + + else { + bid = bidfactory.createBid(CONSTANTS.STATUS.NO_BID); + bid.callback_uid = callbackId; + bid.bidderCode = bidCode; + bidmanager.addBidResponse(placementCode, bid); + } + } + } + + else { + if (bidObj) { + utils.logMessage('No prebid response for placement '+bidObj.placementCode); + } + + else { + utils.logMessage('sekindoUM callback general error'); + } + } + }; + + function _requestBids(bid, callbackId, pubUrl) { + //determine tag params + var spaceId = utils.getBidIdParameter('spaceId', bid.params); + var subId = utils.getBidIdParameter('subId', bid.params); + var bidfloor = utils.getBidIdParameter('bidfloor', bid.params); + var protocol = ('https:' === document.location.protocol ? 's' : ''); + var scriptSrc = 'http'+protocol+'://hb.sekindo.com/live/liveView.php?'; + + scriptSrc = utils.tryAppendQueryString(scriptSrc, 's', spaceId); + scriptSrc = utils.tryAppendQueryString(scriptSrc, 'subId', subId); + scriptSrc = utils.tryAppendQueryString(scriptSrc, 'pubUrl', pubUrl); + scriptSrc = utils.tryAppendQueryString(scriptSrc, 'hbcb', callbackId); + scriptSrc = utils.tryAppendQueryString(scriptSrc, 'hbver', '3'); + scriptSrc = utils.tryAppendQueryString(scriptSrc, 'hbobj', '$$PREBID_GLOBAL$$'); + scriptSrc = utils.tryAppendQueryString(scriptSrc, 'dcpmflr', bidfloor); + scriptSrc = utils.tryAppendQueryString(scriptSrc, 'hbto', $$PREBID_GLOBAL$$.bidderTimeout); + scriptSrc = utils.tryAppendQueryString(scriptSrc, 'protocol', protocol); + + adloader.loadScript(scriptSrc); + } + + return { + callBids: _callBids + }; +}; + +module.exports = sekindoUMAdapter; + diff --git a/test/spec/adapters/sekindoUM_spec.js b/test/spec/adapters/sekindoUM_spec.js new file mode 100644 index 00000000000..ff59b247550 --- /dev/null +++ b/test/spec/adapters/sekindoUM_spec.js @@ -0,0 +1,86 @@ +import {expect} from 'chai'; +import sekindoUMAdapter from '../../../src/adapters/sekindoUM'; +var bidManager = require('src/bidmanager'); + + +describe("sekindoUM Adapter Tests", () => { + + let _sekindoUMAdapter; + var addBidResponseSpy; + + const bidderRequest = { + bidderCode: 'sekindoUM', + bids: [{ + bidder: 'sekindoUM', + bidId: 'sekindo_bidId', + bidderRequestId: 'sekindo_bidderRequestId', + requestId: 'sekindo_requestId', + placementCode: 'foo', + params: { + spaceId: 14071 + } + }] + }; + + beforeEach(() => { + _sekindoUMAdapter = new sekindoUMAdapter(); + }); + + describe('sekindoUM callBids', () => { + + beforeEach(() => { + _sekindoUMAdapter.callBids(bidderRequest); + }); + + it('Verify sekindo script tag was created', () => { + var scriptTags = document.getElementsByTagName('script'); + var sekindoTagExists=0; + for (var i=0; itest ad' + }; + + $$PREBID_GLOBAL$$.sekindoCB(bidderRequest.bids[0].bidId, HB_bid); + var firstBid = addBidResponseSpy.getCall(0).args[1]; + var placementCode1 = addBidResponseSpy.getCall(0).args[0]; + + expect(firstBid.getStatusCode()).to.equal(1); + expect(firstBid.bidderCode).to.equal('sekindoUM'); + expect(firstBid.cpm).to.equal(0.23); + expect(firstBid.ad).to.equal('

test ad

'); + expect(placementCode1).to.equal('foo'); + + expect(addBidResponseSpy.getCalls().length).to.equal(1); + }); + }); + +}); From ec222998281a7751f9d1165397df7831b3f34143 Mon Sep 17 00:00:00 2001 From: Mordhak Date: Tue, 3 Jan 2017 20:38:29 +0100 Subject: [PATCH 03/27] Smart AdServer adapter (#853) * Smart AdServer adapter Add Smart AdServer adapter with tests * fix not supported method Replace startsWith which is not supported in all browser version by lastIndexOf. * Issue with optional parameter Fix issue when no targeting is specified and remove "undefined" value passed in url --- adapters.json | 1 + src/adapters/smartadserver.js | 53 ++++++++++ test/spec/adapters/smartadserver_spec.js | 117 +++++++++++++++++++++++ 3 files changed, 171 insertions(+) create mode 100644 src/adapters/smartadserver.js create mode 100644 test/spec/adapters/smartadserver_spec.js diff --git a/adapters.json b/adapters.json index 6242bd55c1f..dbf33e0b450 100644 --- a/adapters.json +++ b/adapters.json @@ -24,6 +24,7 @@ "pulsepoint", "rhythmone", "rubicon", + "smartadserver", "sekindoUM", "sonobi", "sovrn", diff --git a/src/adapters/smartadserver.js b/src/adapters/smartadserver.js new file mode 100644 index 00000000000..82498dd2787 --- /dev/null +++ b/src/adapters/smartadserver.js @@ -0,0 +1,53 @@ +var utils = require("../utils.js"); +var bidfactory = require("../bidfactory.js"); +var bidmanager = require("../bidmanager.js"); +var adloader = require("src/adloader.js"); +var url = require("url"); + +var SmartAdServer = function SmartAdServer() { + var generateCallback = function(bid) { + var callbackId = "sas_" + utils.getUniqueIdentifierStr(); + $$PREBID_GLOBAL$$[callbackId] = function(adUnit) { + var bidObject; + if (adUnit) { + utils.logMessage(`[SmartAdServer] bid response for placementCode ${bid.placementCode}`); + bidObject = bidfactory.createBid(1); + bidObject.bidderCode = 'smartadserver'; + bidObject.cpm = adUnit.cpm; + bidObject.ad = adUnit.ad; + bidObject.width = adUnit.width; + bidObject.height = adUnit.height; + bidmanager.addBidResponse(bid.placementCode, bidObject); + } else { + utils.logMessage(`[SmartAdServer] no bid response for placementCode ${bid.placementCode}`); + bidObject = bidfactory.createBid(2); + bidObject.bidderCode = 'smartadserver'; + bidmanager.addBidResponse(bid.placementCode, bidObject); + } + }; + return callbackId; + }; + + return { + callBids: function(params) { + for (var i = 0; i < params.bids.length; i++) { + var bid = params.bids[i]; + var adCall = url.parse(bid.params.domain); + adCall.pathname = "/prebid"; + adCall.search = { + "pbjscbk": "pbjs." + generateCallback(bid), + "siteid": bid.params.siteId, + "pgid": bid.params.pageId, + "fmtid": bid.params.formatId, + "tgt": encodeURIComponent(bid.params.target || ''), + "tag": bid.placementCode, + "sizes": bid.sizes.map(size => size[0] + "x" + size[1]).join(","), + "async": 1 + }; + adloader.loadScript(url.format(adCall)); + } + } + }; +}; + +module.exports = SmartAdServer; diff --git a/test/spec/adapters/smartadserver_spec.js b/test/spec/adapters/smartadserver_spec.js new file mode 100644 index 00000000000..d5989d679b1 --- /dev/null +++ b/test/spec/adapters/smartadserver_spec.js @@ -0,0 +1,117 @@ +describe("smartadserver adapter tests", function () { + var urlParse = require("url-parse"); + var querystringify = require("querystringify"); + var adapter = require("src/adapters/smartadserver"); + var adLoader = require("src/adloader"); + var expect = require("chai").expect; + var bidmanager = require("src/bidmanager"); + var CONSTANTS = require('src/constants.json'); + + var DEFAULT_PARAMS = { + bidderCode: "smartadserver", + bids: [{ + bidId: "abcd1234", + sizes: [[300, 250], [300, 200]], + bidder: "smartadserver", + params: { + domain: "http://www.smartadserver.com", + siteId: "1234", + pageId: "5678", + formatId: "90", + target: "test=prebid" + }, + requestId: "efgh5678", + placementCode: "sas_42" + } + ] + }; + + var BID_RESPONSE = { + cpm: 0.42, + ad: "fake ad content", + width: 300, + height: 250 + }; + + it("set url parameters", function () { + var stubLoadScript = sinon.stub(adLoader, "loadScript"); + + adapter().callBids(DEFAULT_PARAMS); + + var smartCallback; + for (var k in $$PREBID_GLOBAL$$) { + if (k.lastIndexOf("sas_", 0) === 0) { + smartCallback = k; + break; + } + } + + var bidUrl = stubLoadScript.getCall(0).args[0]; + var parsedBidUrl = urlParse(bidUrl); + var parsedBidUrlQueryString = querystringify.parse(parsedBidUrl.query); + + expect(parsedBidUrl.hostname).to.equal("www.smartadserver.com"); + expect(parsedBidUrl.pathname).to.equal("/prebid"); + + expect(parsedBidUrlQueryString).to.have.property("pbjscbk").and.to.equal("pbjs." + smartCallback); + expect(parsedBidUrlQueryString).to.have.property("siteid").and.to.equal("1234"); + expect(parsedBidUrlQueryString).to.have.property("pgid").and.to.equal("5678"); + expect(parsedBidUrlQueryString).to.have.property("fmtid").and.to.equal("90"); + expect(parsedBidUrlQueryString).to.have.property("tgt").and.to.equal("test=prebid"); + expect(parsedBidUrlQueryString).to.have.property("tag").and.to.equal("sas_42"); + expect(parsedBidUrlQueryString).to.have.property("sizes").and.to.equal("300x250,300x200"); + expect(parsedBidUrlQueryString).to.have.property("async").and.to.equal("1"); + + stubLoadScript.restore(); + }); + + it("creates an empty bid response if no bids", function() { + var stubLoadScript = sinon.stub(adLoader, "loadScript", function(url) { + var bidUrl = stubLoadScript.getCall(0).args[0]; + var parsedBidUrl = urlParse(bidUrl); + var parsedBidUrlQueryString = querystringify.parse(parsedBidUrl.query); + + pbjs[parsedBidUrlQueryString.pbjscbk.split(".")[1]](null); + }); + var stubAddBidResponse = sinon.stub(bidmanager, "addBidResponse"); + + adapter().callBids(DEFAULT_PARAMS); + + var bidResponsePlacementCode = stubAddBidResponse.getCall(0).args[0]; + var bidResponseAd = stubAddBidResponse.getCall(0).args[1]; + + expect(bidResponsePlacementCode).to.equal(DEFAULT_PARAMS.bids[0].placementCode); + expect(bidResponseAd.getStatusCode()).to.equal(CONSTANTS.STATUS.NO_BID); + expect(bidResponseAd).to.have.property("bidderCode").and.to.equal("smartadserver"); + + stubLoadScript.restore(); + stubAddBidResponse.restore(); + }); + + it("creates a bid response if bid is returned", function() { + var stubLoadScript = sinon.stub(adLoader, "loadScript", function(url) { + var bidUrl = stubLoadScript.getCall(0).args[0]; + var parsedBidUrl = urlParse(bidUrl); + var parsedBidUrlQueryString = querystringify.parse(parsedBidUrl.query); + + pbjs[parsedBidUrlQueryString.pbjscbk.split(".")[1]](BID_RESPONSE); + }); + var stubAddBidResponse = sinon.stub(bidmanager, "addBidResponse"); + + adapter().callBids(DEFAULT_PARAMS); + + var bidResponsePlacementCode = stubAddBidResponse.getCall(0).args[0]; + var bidResponseAd = stubAddBidResponse.getCall(0).args[1]; + + expect(bidResponsePlacementCode).to.equal(DEFAULT_PARAMS.bids[0].placementCode); + expect(bidResponseAd.getStatusCode()).to.equal(CONSTANTS.STATUS.GOOD); + expect(bidResponseAd).to.have.property("bidderCode").and.to.equal("smartadserver"); + expect(bidResponseAd).to.have.property("cpm").and.to.equal(BID_RESPONSE.cpm); + expect(bidResponseAd).to.have.property("ad").and.to.equal(BID_RESPONSE.ad); + expect(bidResponseAd).to.have.property("width").and.to.equal(BID_RESPONSE.width); + expect(bidResponseAd).to.have.property("height").and.to.equal(BID_RESPONSE.height); + + stubLoadScript.restore(); + stubAddBidResponse.restore(); + }); +}); From de91e8ae6f6fa3a10c6a388153bd6037c44b7cb3 Mon Sep 17 00:00:00 2001 From: nativeads Date: Wed, 4 Jan 2017 19:13:01 +0200 Subject: [PATCH 04/27] headbidding alias for adkernel adapter (#883) --- adapters.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/adapters.json b/adapters.json index dbf33e0b450..a49141c8345 100644 --- a/adapters.json +++ b/adapters.json @@ -70,6 +70,11 @@ "appnexus": { "alias": "featureforward" } + }, + { + "adkernel": { + "alias": "headbidding" + } } ] From 4621b515cf155900abb76f468f23f3a8f8c4a668 Mon Sep 17 00:00:00 2001 From: Matt Kendall Date: Wed, 4 Jan 2017 13:43:12 -0500 Subject: [PATCH 05/27] Update README.md (#904) --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index de3aa030015..19679ddf3ef 100644 --- a/README.md +++ b/README.md @@ -31,13 +31,13 @@ If you experience errors after a version update, try a fresh install: -## Build +## Build for Dev To build the project on your local machine, run: - $ gulp build + $ gulp serve -This runs some code quality checks and generates the following files: +This runs some code quality checks, starts a web server at `http://localhost:9999` serving from the project root and generates the following files: + `./build/dev/prebid.js` - Full source code for dev and debug + `./build/dev/prebid.js.map` - Source map for dev and debug @@ -91,7 +91,7 @@ Having said that, you are probably safe to check your custom bundle into your pr -## Run +## Test locally To configure Prebid.js to run locally, edit the example file `./integrationExamples/gpt/pbjs_example_gpt.html`: From d1153d7aabeb15d13c057c94e5d1d8f58d73275c Mon Sep 17 00:00:00 2001 From: Matt Lane Date: Sat, 7 Jan 2017 12:29:01 -0800 Subject: [PATCH 06/27] Add testing section to contribution guide (#910) * Add testing section * Point link to CONTRIBUTING testing section --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- CONTRIBUTING.md | 84 +++++++++++++++++++++++++++++++- 2 files changed, 83 insertions(+), 3 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 175cc2a4705..fd757dce1f5 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,5 +1,5 @@ ## Type of change diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1de83fccb46..b2895cd8982 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,11 +1,91 @@ # Contributing to Prebid.js -Thank you for taking the time to contribute to Prebid.js! +Contributions are always welcome. To contribute, [fork](https://help.github.com/articles/fork-a-repo/) Prebid.js, commit your changes, and [open a pull request](https://help.github.com/articles/using-pull-requests/). ## Pull Requests -Please make sure that pull requests are scoped to one change, and that any added or changed code includes tests with greater than 80% code coverage. See [Testing Prebid.js](http://prebid.org/dev-docs/testing-prebid.html) for help on writing tests. +Please make sure that pull requests are scoped to one change, and that any added or changed code includes tests with greater than 80% code coverage. See [Testing Prebid.js](#testing-prebidjs) for help on writing tests. ## Issues [prebid.org](http://prebid.org/) contains documentation that may help answer questions you have about using Prebid.js. If you can't find the answer there, try searching for a similar issue on the [issues page](https://github.com/prebid/Prebid.js/issues). If you don't find an answer there, [open a new issue](https://github.com/prebid/Prebid.js/issues/new). ## Documentation If you have a documentation issue or pull request, please open a ticket or PR in the [documentation repository](https://github.com/prebid/prebid.github.io). + +## Testing Prebid.js +Pull requests to the Prebid.js library will need to include tests with greater than 80% code coverage for any changed/added code before they can be merged into master. + +This section describes how to test code in the Prebid.js repository to help prepare your pull request. + +### Writing tests +When you are adding code to Prebid.js, or modifying code that isn't covered by an existing test, test the code according to these guidelines: + +- If the module you are working on is already partially tested by a file within the `test` directory, add tests to that file +- If the module does not have any tests, create a new test file +- Group tests in a `describe` block +- Test individual units of code within an `it` block +- Within an `it` block, it may be helpful to use the "Arrange-Act-Assert" pattern + - _Arrange_: set up necessary preconditions and inputs + - e.g., creating objects, spies, etc. + - _Act_: call or act on the unit under test + - e.g., call the function you are testing with the parameters you set up + - _Assert_: check that the expected results have occurred + - e.g., use Chai assertions to check that the expected output is equal to the actual output +- Test the public interface, not the internal implementation +- If using global `pbjs` data structures in your test, take care to not completely overwrite them with your own data as that may affect other tests relying on those structures, e.g.: + - **OK**: `pbjs._bidsRequested.push(bidderRequestObject);` + - **NOT OK**: `pbjs._bidsRequested = [bidderRequestObject];` +- If you need to check `adloader.loadScript` in a test, use a `stub` rather than a `spy`. `spy`s trigger a network call which can result in a `script error` and cause unrelated unit tests to fail. `stub`s will let you gather information about the `adloader.loadScript` call without affecting external resources +- When writing tests you may use ES2015 syntax if desired + +### Running tests +After checking out the Prebid.js repository and installing dev dependencies with `npm install`, use the following commands to run tests as you are working on code: + +- `gulp test` will run the test suite once (`npm test` is aliased to call `gulp test`) +- `gulp serve` will run tests once and stay open, re-running tests whenever a file in the `src` or `test` directory is modified + +### Checking results and code coverage +Check the test results using these guidelines: + +- Look at the total number of tests run, passed, and failed in the shell window. +- If all tests are passing, great. +- Otherwise look for errors printed in the console for a description of the failing test. +- You may need to iterate on your code or tests until all tests are passing. +- Make sure existing tests still pass. +- There is a table below the testing report that shows code coverage percentage, for each file under the `src` directory. +- Each time you run tests, a code coverage report is generated in `build/coverage/lcov/lcov-report/index.html`. +- This is a static HTML page that you can load in your browser. +- On that page, navigate to the file you are testing to see which lines are being tested. +- Red indicates that a line isn't covered by a test. +- Gray indicates a line that doesn't need coverage, such as a comment or blank line. +- Green indicates a line that is covered by tests. +- The code you have added or modified must have greater than 80% coverage to be accepted. + +### Examples +Prebid.js already has lots of tests. Read them to see how Prebid.js is tested, and for inspiration: + +- Look in `test/spec` and its subdirectories +- Tests for bidder adaptors are located in `test/spec/adapters` + +A test module might have the following general structure: + +```JavaScript +// import or require modules necessary for the test, e.g.: +import { expect } from 'chai'; // may prefer 'assert' in place of 'expect' +import adapter from 'src/adapters/'; + +describe('', () => { + it('', () => { + // Arrange - set up preconditions and inputs + // Act - call or act on the code under test + // Assert - use chai to check that expected results have occurred + }); + + // Add other `describe` or `it` blocks as necessary +}); +``` + +### Resources +The Prebid.js testing stack contains some of the following tools. It may be helpful to consult their documentation during the testing process. + +- [Mocha - test framework](http://mochajs.org/) +- [Chai - BDD/TDD assertion library](http://chaijs.com/) +- [Sinon - spy, stub, and mock library](http://sinonjs.org/) From 08536779e376d1835f743d4152875567f5b90348 Mon Sep 17 00:00:00 2001 From: lntho Date: Tue, 10 Jan 2017 11:35:25 -0800 Subject: [PATCH 07/27] OpenX official adapter release: Fixing the existing openx adapter and adding test coverage on it (#896) --- src/adapters/openx.js | 308 ++++++++++++++++++++----------- test/spec/adapters/openx_spec.js | 195 +++++++++++++++++++ 2 files changed, 400 insertions(+), 103 deletions(-) create mode 100644 test/spec/adapters/openx_spec.js diff --git a/src/adapters/openx.js b/src/adapters/openx.js index 4e8704235d4..7033ee5482a 100644 --- a/src/adapters/openx.js +++ b/src/adapters/openx.js @@ -1,129 +1,231 @@ -// jshint ignore:start -var bidfactory = require('../bidfactory.js'); -var bidmanager = require('../bidmanager.js'); -var adloader = require('../adloader'); - -/** - * Adapter for requesting bids from OpenX. - * - * @param {Object} options - Configuration options for OpenX - * @param {string} options.pageURL - Current page URL to send with bid request - * @param {string} options.refererURL - Referer URL to send with bid request - * - * @returns {{callBids: _callBids}} - * @constructor - */ -var OpenxAdapter = function OpenxAdapter(options) { - - var opts = options || {}; - var scriptUrl; - var bids; - - function _callBids(params) { - bids = params.bids || []; - for (var i = 0; i < bids.length; i++) { - var bid = bids[i]; - - //load page options from bid request - if (bid.params.pageURL) { - opts.pageURL = bid.params.pageURL; - } +const bidfactory = require('../bidfactory.js'); +const bidmanager = require('../bidmanager.js'); +const adloader = require('../adloader'); +const CONSTANTS = require('../constants.json'); +const utils = require('../utils.js'); + +const OpenxAdapter = function OpenxAdapter() { + const BIDDER_CODE = 'openx'; + const BIDDER_CONFIG = 'hb_pb'; + let startTime; + + let pdNode = null; + + $$PREBID_GLOBAL$$.oxARJResponse = function (oxResponseObj) { + let adUnits = oxResponseObj.ads.ad; + if (oxResponseObj.ads && oxResponseObj.ads.pixels) { + makePDCall(oxResponseObj.ads.pixels); + } - if (bid.params.refererURL) { - opts.refererURL = bid.params.refererURL; + if (!adUnits) { + adUnits = []; + } + + let bids = $$PREBID_GLOBAL$$._bidsRequested.find(bidSet => bidSet.bidderCode === 'openx').bids; + for (let i = 0; i < bids.length; i++) { + let bid = bids[i]; + let auid = null; + let adUnit = null; + // find the adunit in the response + for (let j = 0; j < adUnits.length; j++) { + adUnit = adUnits[j]; + if (String(bid.params.unit) === String(adUnit.adunitid) && adUnitHasValidSizeFromBid(adUnit,bid) && !adUnit.used) { + auid = adUnit.adunitid; + break; + } } - if (bid.params.jstag_url) { - scriptUrl = bid.params.jstag_url; + let beaconParams = { + bd: +(new Date()) - startTime, + br: '0', // maybe 0, t, or p + bt: $$PREBID_GLOBAL$$.cbTimeout || $$PREBID_GLOBAL$$.bidderTimeout , // For the timeout per bid request + bs: window.location.hostname + }; + + // no fill :( + if (!auid) { + addBidResponse(null, bid); + continue; } + adUnit.used = true; - if (bid.params.pgid) { - opts.pgid = bid.params.pgid; + if (adUnit.pub_rev) { + beaconParams.br = beaconParams.bt < beaconParams.bd ? 't' : 'p'; + beaconParams.bp = adUnit.pub_rev; + beaconParams.ts = adUnit.ts; + addBidResponse(adUnit, bid); } + buildBoPixel(adUnit.creative[0], beaconParams); + } + }; + + function getViewportDimensions(isIfr) { + let width, + height, + tWin = window, + tDoc = document, + docEl = tDoc.documentElement, + body; + + if (isIfr) { + tWin = window.top; + tDoc = window.top.document; + docEl = tDoc.documentElement; + body = tDoc.body; + + width = tWin.innerWidth || docEl.clientWidth || body.clientWidth; + height = tWin.innerHeight || docEl.clientHeight || body.clientHeight; + } else { + docEl = tDoc.documentElement; + width = tWin.innerWidth || docEl.clientWidth; + height = tWin.innerHeight || docEl.clientHeight; } - _requestBids(); + return `${width}x${height}`; } - function _requestBids() { + function makePDCall(pixelsUrl) { + let pdFrame = utils.createInvisibleIframe(); + let name = 'openx-pd'; + pdFrame.setAttribute("id", name); + pdFrame.setAttribute("name", name); + let rootNode = document.body; - if (scriptUrl) { - adloader.loadScript(scriptUrl, function () { - var i; - var POX = OX(); + if (!rootNode) { + return; + } - if (opts.pageURL) { - POX.setPageURL(opts.pageURL); - } + pdFrame.src = pixelsUrl; - if (opts.refererURL) { - POX.setRefererURL(opts.refererURL); + if (pdNode) { + pdNode.parentNode.replaceChild(pdFrame, pdNode); + pdNode = pdFrame; + } else { + pdNode = rootNode.appendChild(pdFrame); + } + } + + function addBidResponse(adUnit, bid) { + let bidResponse = bidfactory.createBid(adUnit ? CONSTANTS.STATUS.GOOD : CONSTANTS.STATUS.NO_BID, bid); + bidResponse.bidderCode = BIDDER_CODE; + + if (adUnit) { + let creative = adUnit.creative[0]; + bidResponse.ad = adUnit.html; + bidResponse.cpm = Number(adUnit.pub_rev) / 1000; + bidResponse.ad_id = adUnit.adid; + if (adUnit.deal_id) { + bidResponse.dealId = adUnit.deal_id; + } + if (creative) { + bidResponse.width = creative.width; + bidResponse.height = creative.height; + } + } + bidmanager.addBidResponse(bid.placementCode, bidResponse); + } + + function buildQueryStringFromParams(params) { + for (let key in params) { + if (params.hasOwnProperty(key)) { + if (!params[key]) { + delete params[key]; } + } + } + return utils._map(Object.keys(params), key => `${key}=${params[key]}`) + .join('&'); + } + + function buildBoPixel(creative, params) { + let img = new Image(); + let recordPixel = creative.tracking.impression; + let boBase = recordPixel.match(/([^?]+\/)ri\?/); + + if (boBase) { + img.src = `${boBase[1]}bo?${buildQueryStringFromParams(params)}`; + } + } - if (opts.pgid) { - POX.addPage(opts.pgid); + function adUnitHasValidSizeFromBid(adUnit, bid) { + let sizes = utils.parseSizesInput(bid.sizes); + let sizeLength = sizes && sizes.length || 0; + let found = false; + let creative = adUnit.creative && adUnit.creative[0]; + let creative_size = String(creative.width) + 'x' + String(creative.height); + + if (utils.isArray(sizes)) { + for (let i = 0; i < sizeLength; i++) { + let size = sizes[i]; + if (String(size) === String(creative_size)) { + found = true; + break; } + } + } + return found; + } - // Add each ad unit ID - for (i = 0; i < bids.length; i++) { - POX.addAdUnit(bids[i].params.unit); + function buildRequest(bids, params, delDomain) { + if (!utils.isArray(bids)) { + return; + } + + params.auid = utils._map(bids, bid => bid.params.unit).join('%2C'); + params.aus = utils._map(bids, bid => { + return utils.parseSizesInput(bid.sizes).join(','); + }).join('|'); + + bids.forEach(function (bid) { + for (let customParam in bid.params.customParams) { + if (bid.params.customParams.hasOwnProperty(customParam)) { + params["c." + customParam] = bid.params.customParams[customParam]; } + } + }); - POX.addHook(function (response) { - var i; - var bid; - var adUnit; - var adResponse; - - // Map each bid to its response - for (i = 0; i < bids.length; i++) { - bid = bids[i]; - - // Get ad response - adUnit = response.getOrCreateAdUnit(bid.params.unit); - // If 'pub_rev' (CPM) isn't returned we got an empty response - if (adUnit.get('pub_rev')) { - adResponse = adResponse = bidfactory.createBid(1); - - adResponse.bidderCode = 'openx'; - adResponse.ad_id = adUnit.get('ad_id'); - adResponse.cpm = Number(adUnit.get('pub_rev')) / 1000; - - adResponse.ad = adUnit.get('html'); - if(adUnit.get('deal_id') !== undefined) { - adResponse.dealId = adUnit.get('deal_id'); - } - - // Add record/impression pixel to the creative HTML - var recordPixel = OX.utils.template(response.getRecordTemplate(), { - medium: OX.utils.getMedium(), - rtype: OX.Resources.RI, - txn_state: adUnit.get('ts') - }); - adResponse.ad += '
'; - - adResponse.adUrl = adUnit.get('ad_url'); - adResponse.width = adUnit.get('width'); - adResponse.height = adUnit.get('height'); - - bidmanager.addBidResponse(bid.placementCode, adResponse); - } else { - // Indicate an ad was not returned - adResponse = bidfactory.createBid(2); - adResponse.bidderCode = 'openx'; - bidmanager.addBidResponse(bid.placementCode, adResponse); - } - } - }, OX.Hooks.ON_AD_RESPONSE); - - // Make request - POX.load(); - }, true); + params.callback = 'window.$$PREBID_GLOBAL$$.oxARJResponse'; + let queryString = buildQueryStringFromParams(params); + + adloader.loadScript(`//${delDomain}/w/1.0/arj?${queryString}`); + } + + function callBids(params) { + let isIfr, + bids = params.bids || [], + currentURL = window.location.href && encodeURIComponent(window.location.href); + try { + isIfr = window.self !== window.top; + } + catch (e) { + isIfr = false; } + if (bids.length === 0) { + return; + } + + let delDomain = bids[0].params.delDomain; + + startTime = new Date(params.start); + + buildRequest(bids, { + ju: currentURL, + jr: currentURL, + ch: document.charSet || document.characterSet, + res: `${screen.width}x${screen.height}x${screen.colorDepth}`, + ifr: isIfr, + tz: startTime.getTimezoneOffset(), + tws: getViewportDimensions(isIfr), + ee: 'api_sync_write', + ef: 'bt%2Cdb', + be: 1, + bc: BIDDER_CONFIG + }, + delDomain); } return { - callBids: _callBids + callBids: callBids }; }; diff --git a/test/spec/adapters/openx_spec.js b/test/spec/adapters/openx_spec.js new file mode 100644 index 00000000000..f66b9029aa3 --- /dev/null +++ b/test/spec/adapters/openx_spec.js @@ -0,0 +1,195 @@ +describe('openx adapter tests', function () { + + const expect = require('chai').expect; + const assert = require('chai').assert; + const adapter = require('src/adapters/openx'); + const bidmanager = require('src/bidmanager'); + const adloader = require('src/adloader'); + const CONSTANTS = require('src/constants.json'); + + describe('test openx callback responce', function () { + + it('should exist and be a function', function () { + expect(pbjs.oxARJResponse).to.exist.and.to.be.a('function'); + }); + + it('should add empty bid responses if no bids returned', function () { + let stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); + + let bidderRequest = { + bidderCode: 'openx', + bids: [ + { + bidId: 'bidId1', + bidder: 'openx', + params: { + delDomain: 'delDomain1', + unit: '1234' + }, + sizes: [[300, 250]], + placementCode: 'test-gpt-div-1234' + } + ] + }; + + // empty ads in bidresponse + let response = { + "ads": + { + "version": 1, + "count": 1, + "pixels": "http://testpixels.net", + "ad": [] + } + }; + + pbjs._bidsRequested.push(bidderRequest); + // adapter needs to be called, in order for the stub to register. + adapter(); + + pbjs.oxARJResponse(response); + + let bidPlacementCode1 = stubAddBidResponse.getCall(0).args[0]; + let bidResponse1 = stubAddBidResponse.getCall(0).args[1]; + expect(bidPlacementCode1).to.equal('test-gpt-div-1234'); + expect(bidResponse1.getStatusCode()).to.equal(CONSTANTS.STATUS.NO_BID); + expect(bidResponse1.bidderCode).to.equal('openx'); + stubAddBidResponse.restore(); + }); + }); + + it('should add bid responses if bids are returned', function () { + let stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); + + let bidderRequest = { + bidderCode: 'openx', + bids: [ + { + bidId: 'bidId1', + bidder: 'openx', + params: { + delDomain: 'delDomain1', + unit: '1234' + }, + sizes: [[300, 250]], + placementCode: 'test-gpt-div-1234' + } + ] + }; + + // empty ads in bidresponse + let response = { + "ads": + { + "version": 1, + "count": 1, + "pixels": "http://testpixels.net", + "ad": [ + { + "adunitid": 1234, + "adid": 5678, + "type": "html", + "html": "test_html", + "framed": 1, + "is_fallback": 0, + "ts": "ts", + "cpipc": 1000, + "pub_rev": "1000", + "adv_id": "adv_id", + "brand_id": "", + "creative": [ + { + "width": "300", + "height": "250", + "target": "_blank", + "mime": "text/html", + "media": "test_media", + "tracking": { + "impression": "test_impression", + "inview": "test_inview", + "click": "test_click" + } + } + ] + }] + } + }; + + pbjs._bidsRequested.push(bidderRequest); + // adapter needs to be called, in order for the stub to register. + adapter(); + + pbjs.oxARJResponse(response); + + let bidPlacementCode1 = stubAddBidResponse.getCall(0).args[0]; + let bidResponse1 = stubAddBidResponse.getCall(0).args[1]; + let bid1width = '300'; + let bid1height = '250'; + let cpm = 1; + expect(bidPlacementCode1).to.equal('test-gpt-div-1234'); + expect(bidResponse1.getStatusCode()).to.equal(CONSTANTS.STATUS.GOOD); + expect(bidResponse1.bidderCode).to.equal('openx'); + expect(bidResponse1.width).to.equal(bid1width); + expect(bidResponse1.height).to.equal(bid1height); + expect(bidResponse1.cpm).to.equal(cpm); + stubAddBidResponse.restore(); + }); + + it('should not call loadscript when inputting with empty params', function () { + let spyLoadScript = sinon.spy(adloader, 'loadScript'); + adapter().callBids({}); + assert(!spyLoadScript.called); + spyLoadScript.restore(); + }); + + it('should call loadscript with the correct params', function () { + let spyLoadScript = sinon.spy(adloader, 'loadScript'); + let params = { + bids: [ + { + sizes: [[300, 250], [300, 600]], + params: { + delDomain: 'testdelDomain', + unit: 1234 + } + } + ] + }; + adapter().callBids(params); + + sinon.assert.calledOnce(spyLoadScript); + + let bidUrl = spyLoadScript.getCall(0).args[0]; + expect(bidUrl).to.include('testdelDomain'); + expect(bidUrl).to.include('1234'); + expect(bidUrl).to.include('300x250,300x600'); + spyLoadScript.restore(); + }); + + it('should send out custom params on bids that have customParams specified', function () { + let spyLoadScript = sinon.spy(adloader, 'loadScript'); + let params = { + bids: [ + { + sizes: [[300, 250], [300, 600]], + params: { + delDomain: 'testdelDomain', + unit: 1234, + customParams: {'test1': 'testval1'} + } + } + ] + }; + adapter().callBids(params); + + sinon.assert.calledOnce(spyLoadScript); + + let bidUrl = spyLoadScript.getCall(0).args[0]; + expect(bidUrl).to.include('testdelDomain'); + expect(bidUrl).to.include('1234'); + expect(bidUrl).to.include('300x250,300x600'); + expect(bidUrl).to.include('c.test1=testval1'); + spyLoadScript.restore(); + }); + +}); From a42655b23c1ad96c32939e753737431fdeb8a220 Mon Sep 17 00:00:00 2001 From: rizhang Date: Thu, 12 Jan 2017 11:06:46 -0800 Subject: [PATCH 08/27] Add Sharethrough adapter (#865) * Rz/submit to prebid (#4) Added Sharethrough Adapter * fix warnings * added beacons * made compatible with chrome 37. other minor changes * win beacon fired in analytics adapter * specs for new analytics adapter * add try catch blocks. misc refactor * removed test page * remove debugger * refactor analytics adapter * removed test endpoint * analytics url parameter is empty * removed bidwon listener on adapter * removed analytics from package.json --- adapters.json | 1 + .../analytics/sharethrough_analytics.js | 63 +++++++ src/adapters/sharethrough.js | 127 +++++++++++++ test/spec/adapters/sharethrough_spec.js | 178 ++++++++++++++++++ .../analytics/sharethrough_analytics_spec.js | 99 ++++++++++ 5 files changed, 468 insertions(+) create mode 100644 src/adapters/analytics/sharethrough_analytics.js create mode 100644 src/adapters/sharethrough.js create mode 100644 test/spec/adapters/sharethrough_spec.js create mode 100644 test/spec/unit/adapters/analytics/sharethrough_analytics_spec.js diff --git a/adapters.json b/adapters.json index a49141c8345..e4fd2afe9fd 100644 --- a/adapters.json +++ b/adapters.json @@ -38,6 +38,7 @@ "underdogmedia", "memeglobal", "centro", + "sharethrough", "roxot", "vertoz", "widespace", diff --git a/src/adapters/analytics/sharethrough_analytics.js b/src/adapters/analytics/sharethrough_analytics.js new file mode 100644 index 00000000000..6b4f9297346 --- /dev/null +++ b/src/adapters/analytics/sharethrough_analytics.js @@ -0,0 +1,63 @@ +import adapter from 'AnalyticsAdapter'; +const utils = require('../../utils'); + +const emptyUrl = ''; +const analyticsType = 'endpoint'; +const STR_BIDDER_CODE = "sharethrough"; +const STR_VERSION = "0.1.0"; + +export default utils.extend(adapter( + { + emptyUrl, + analyticsType + } +), + { + STR_BEACON_HOST: document.location.protocol + "//b.sharethrough.com/butler?", + placementCodeSet: {}, + + track({ eventType, args }) { + if(eventType === 'bidRequested' && args.bidderCode === 'sharethrough') { + var bids = args.bids; + var keys = Object.keys(bids); + for(var i = 0; i < keys.length; i++) { + this.placementCodeSet[bids[keys[i]].placementCode] = args.bids[keys[i]]; + } + } + + if(eventType === 'bidWon') { + this.bidWon(args); + } + }, + + bidWon(args) { + const curBidderCode = args.bidderCode; + + if(curBidderCode !== STR_BIDDER_CODE && (args.adUnitCode in this.placementCodeSet)) { + let strBid = this.placementCodeSet[args.adUnitCode]; + this.fireLoseBeacon(curBidderCode, args.cpm, strBid.adserverRequestId, "headerBidLose"); + } + }, + + fireLoseBeacon(winningBidderCode, winningCPM, arid, type) { + let loseBeaconUrl = this.STR_BEACON_HOST; + loseBeaconUrl = utils.tryAppendQueryString(loseBeaconUrl, "winnerBidderCode", winningBidderCode); + loseBeaconUrl = utils.tryAppendQueryString(loseBeaconUrl, "winnerCpm", winningCPM); + loseBeaconUrl = utils.tryAppendQueryString(loseBeaconUrl, "arid", arid); + loseBeaconUrl = utils.tryAppendQueryString(loseBeaconUrl, "type", type); + loseBeaconUrl = this.appendEnvFields(loseBeaconUrl); + + this.fireBeacon(loseBeaconUrl); + }, + appendEnvFields(url) { + url = utils.tryAppendQueryString(url, 'hbVersion', '$prebid.version$'); + url = utils.tryAppendQueryString(url, 'strVersion', STR_VERSION); + url = utils.tryAppendQueryString(url, 'hbSource', 'prebid'); + + return url; + }, + fireBeacon(theUrl) { + const img = new Image(); + img.src = theUrl; + } +}); diff --git a/src/adapters/sharethrough.js b/src/adapters/sharethrough.js new file mode 100644 index 00000000000..2732d714479 --- /dev/null +++ b/src/adapters/sharethrough.js @@ -0,0 +1,127 @@ +var utils = require('../utils.js'); +var bidmanager = require('../bidmanager.js'); +var bidfactory = require('../bidfactory.js'); + +const STR_BIDDER_CODE = "sharethrough"; +const STR_VERSION = "0.1.0"; //Need to check analytics too for version + +var SharethroughAdapter = function SharethroughAdapter() { + + const str = {}; + str.STR_BTLR_HOST = document.location.protocol + "//btlr.sharethrough.com"; + str.STR_BEACON_HOST = document.location.protocol + "//b.sharethrough.com/butler?"; + str.placementCodeSet = {}; + + function _callBids(params) { + const bids = params.bids; + + addEventListener("message", _receiveMessage, false); + + // cycle through bids + for (let i = 0; i < bids.length; i += 1) { + const bidRequest = bids[i]; + str.placementCodeSet[bidRequest.placementCode] = bidRequest; + const scriptUrl = _buildSharethroughCall(bidRequest); + str.loadIFrame(scriptUrl); + } + } + + function _buildSharethroughCall(bid) { + const testPkey = 'test'; + const pkey = utils.getBidIdParameter('pkey', bid.params); + + let host = str.STR_BTLR_HOST; + + let url = host + "/header-bid/v1?"; + url = utils.tryAppendQueryString(url, 'bidId', bid.bidId); + + if(pkey !== testPkey) { + url = utils.tryAppendQueryString(url, 'placement_key', pkey); + url = utils.tryAppendQueryString(url, 'ijson', '$$PREBID_GLOBAL$$.strcallback'); + url = appendEnvFields(url); + } else { + url = url.substring(0, url.length - 1); + } + + return url; + } + + str.loadIFrame = function(url) { + const iframe = document.createElement("iframe"); + iframe.src = url; + iframe.style.cssText = 'display:none;'; + + document.body.appendChild(iframe); + }; + + function _receiveMessage(event) { + if(event.origin === str.STR_BTLR_HOST) { + try { + $$PREBID_GLOBAL$$.strcallback(JSON.parse(event.data).response); + } catch(e) { + console.log(e); + } + } + } + + $$PREBID_GLOBAL$$.strcallback = function(bidResponse) { + const bidId = bidResponse.bidId; + const bidObj = utils.getBidRequest(bidId); + try { + const bid = bidfactory.createBid(1, bidObj); + bid.bidderCode = STR_BIDDER_CODE; + bid.cpm = bidResponse.creatives[0].cpm; + const size = bidObj.sizes[0]; + bid.width = size[0]; + bid.height = size[1]; + bid.adserverRequestId = bidResponse.adserverRequestId; + str.placementCodeSet[bidObj.placementCode].adserverRequestId = bidResponse.adserverRequestId; + + bid.pkey = utils.getBidIdParameter('pkey', bidObj.params); + + const windowLocation = `str_response_${bidId}`; + const bidJsonString = JSON.stringify(bidResponse); + bid.ad = `
+
+ + + `; + bidmanager.addBidResponse(bidObj.placementCode, bid); + } catch (e) { + _handleInvalidBid(bidObj); + } + }; + + function _handleInvalidBid(bidObj) { + const bid = bidfactory.createBid(2, bidObj); + bidmanager.addBidResponse(bidObj.placementCode, bid); + } + + function appendEnvFields(url) { + url = utils.tryAppendQueryString(url, 'hbVersion', '$prebid.version$'); + url = utils.tryAppendQueryString(url, 'strVersion', STR_VERSION); + url = utils.tryAppendQueryString(url, 'hbSource', 'prebid'); + + return url; + } + + return { + callBids: _callBids, + str : str, + }; +}; + +module.exports = SharethroughAdapter; + diff --git a/test/spec/adapters/sharethrough_spec.js b/test/spec/adapters/sharethrough_spec.js new file mode 100644 index 00000000000..83e207e0199 --- /dev/null +++ b/test/spec/adapters/sharethrough_spec.js @@ -0,0 +1,178 @@ +import { expect } from 'chai'; +import Adapter from '../../../src/adapters/sharethrough'; +import bidManager from '../../../src/bidmanager'; + +describe('sharethrough adapter', () => { + + let adapter; + let sandbox; + let bidsRequestedOriginal; + + const bidderRequest = { + bidderCode: 'sharethrough', + bids: [ + { + bidder: 'sharethrough', + bidId: 'bidId1', + sizes: [[600,300]], + placementCode: 'foo', + params: { + pkey: 'aaaa1111' + } + }, + { + bidder: 'sharethrough', + bidId: 'bidId2', + sizes: [[700,400]], + placementCode: 'bar', + params: { + pkey: 'bbbb2222' + } + } + ] + }; + + beforeEach(() => { + adapter = new Adapter(); + sandbox = sinon.sandbox.create(); + bidsRequestedOriginal = pbjs._bidsRequested; + pbjs._bidsRequested = []; + }); + + afterEach(() => { + sandbox.restore(); + + pbjs._bidsRequested = bidsRequestedOriginal; + }); + + describe('callBids', () => { + + let firstBidUrl; + let secondBidUrl; + + beforeEach(() => { + sandbox.spy(adapter.str, 'loadIFrame'); + }); + + it('should call loadIFrame on the adloader for each bid', () => { + + adapter.callBids(bidderRequest); + + firstBidUrl = adapter.str.loadIFrame.firstCall.args[0]; + secondBidUrl = adapter.str.loadIFrame.secondCall.args[0]; + + sinon.assert.calledTwice(adapter.str.loadIFrame); + + expect(firstBidUrl).to.contain(adapter.str.STR_BTLR_HOST + '/header-bid/v1?bidId=bidId1&placement_key=aaaa1111&ijson=pbjs.strcallback&hbVersion=%24prebid.version%24&strVersion=0.1.0&hbSource=prebid&'); + expect(secondBidUrl).to.contain(adapter.str.STR_BTLR_HOST + '/header-bid/v1?bidId=bidId2&placement_key=bbbb2222&ijson=pbjs.strcallback&hbVersion=%24prebid.version%24&strVersion=0.1.0&hbSource=prebid&'); + }); + + }); + + describe('strcallback', () => { + + it('should exist and be a function', () => { + let shit = sandbox.stub(pbjs, 'strcallback'); + expect(pbjs.strcallback).to.exist.and.to.be.a('function'); + }); + + }); + + describe('bid requests', () => { + + let firstBid; + let secondBid; + + beforeEach(() => { + sandbox.stub(bidManager, 'addBidResponse'); + + pbjs._bidsRequested.push(bidderRequest); + adapter.str.placementCodeSet['foo'] = {}; + adapter.str.placementCodeSet['bar'] = {}; + // respond + + let bidderReponse1 = { + "adserverRequestId": "40b6afd5-6134-4fbb-850a-bb8972a46994", + "bidId": "bidId1", + "creatives": [ + { + "cpm": 12.34, + "auctionWinId": "b2882d5e-bf8b-44da-a91c-0c11287b8051", + "version": 1 + } + ], + "stxUserId": "" + }; + + let bidderReponse2 = { + "adserverRequestId": "40b6afd5-6134-4fbb-850a-bb8972a46994", + "bidId": "bidId2", + "creatives": [ + { + "cpm": 12.35, + "auctionWinId": "b2882d5e-bf8b-44da-a91c-0c11287b8051", + "version": 1 + } + ], + "stxUserId": "" + }; + + pbjs.strcallback(bidderReponse1); + pbjs.strcallback(bidderReponse2); + + firstBid = bidManager.addBidResponse.firstCall.args[1]; + secondBid = bidManager.addBidResponse.secondCall.args[1]; + }); + + it('should add a bid object for each bid', () => { + sinon.assert.calledTwice(bidManager.addBidResponse); + }); + + it('should pass the correct placement code as first param', () => { + let firstPlacementCode = bidManager.addBidResponse.firstCall.args[0]; + let secondPlacementCode = bidManager.addBidResponse.secondCall.args[0]; + + expect(firstPlacementCode).to.eql('foo'); + expect(secondPlacementCode).to.eql('bar'); + }); + + it('should include the bid request bidId as the adId', () => { + expect(firstBid).to.have.property('adId', 'bidId1'); + expect(secondBid).to.have.property('adId', 'bidId2'); + }); + + it('should have a good statusCode', () => { + expect(firstBid.getStatusCode()).to.eql(1); + expect(secondBid.getStatusCode()).to.eql(1); + }); + + it('should add the CPM to the bid object', () => { + expect(firstBid).to.have.property('cpm', 12.34); + expect(secondBid).to.have.property('cpm', 12.35); + }); + + it('should add the bidder code to the bid object', () => { + expect(firstBid).to.have.property('bidderCode', 'sharethrough'); + expect(secondBid).to.have.property('bidderCode', 'sharethrough'); + }); + + it('should include the ad on the bid object', () => { + expect(firstBid).to.have.property('ad'); + expect(secondBid).to.have.property('ad'); + }); + + it('should include the size on the bid object', () => { + expect(firstBid).to.have.property('width', 600); + expect(firstBid).to.have.property('height', 300); + expect(secondBid).to.have.property('width', 700); + expect(secondBid).to.have.property('height', 400); + }); + + it('should include the pkey', () => { + expect(firstBid).to.have.property('pkey', 'aaaa1111'); + expect(secondBid).to.have.property('pkey', 'bbbb2222'); + }); + + }); + +}); diff --git a/test/spec/unit/adapters/analytics/sharethrough_analytics_spec.js b/test/spec/unit/adapters/analytics/sharethrough_analytics_spec.js new file mode 100644 index 00000000000..81a46205d88 --- /dev/null +++ b/test/spec/unit/adapters/analytics/sharethrough_analytics_spec.js @@ -0,0 +1,99 @@ +import sharethroughAnalytics from 'src/adapters/analytics/sharethrough_analytics'; +import { expect } from 'chai'; + +describe('sharethrough analytics adapter', () => { + let sandbox; + + beforeEach(() =>{ + sandbox = sinon.sandbox.create(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + describe('track', () => { + + describe('when event type is bidRequested', () => { + + beforeEach(() => { + let eventType = 'bidRequested'; + let args = {"bidderCode" : "sharethrough", "bids":{"0" : {"placementCode" : "fake placement Code"}}}; + sharethroughAnalytics.track({eventType, args}) + }); + + it('placementCodeSet contains a value', () => { + expect(sharethroughAnalytics.placementCodeSet["fake placement Code"] == undefined).to.equal(false) + }); + }); + + }); + + describe('bid won handler', () => { + + let fireLoseBeaconStub; + + beforeEach(() => { + fireLoseBeaconStub = sandbox.stub(sharethroughAnalytics, 'fireLoseBeacon'); + }); + + describe('when bidderCode is not sharethrough and sharethrough is in bid', () => { + beforeEach(() => { + sharethroughAnalytics.placementCodeSet["div-gpt-ad-1460505748561-0"] = {"adserverRequestId" : "0eca470d-fcac-48e6-845a-c86483ccaa0c"} + var args = { + "bidderCode": "someoneelse", + "width": 600, + "height": 300, + "statusMessage": "Bid available", + "adId": "23fbe93a90c924", + "cpm": 3.984986853301525, + "adserverRequestId": "0eca470d-fcac-48e6-845a-c86483ccaa0c", + "winId": "1c404469-f7bb-4e50-b6f6-a8eaf0808999", + "pkey": "xKcxTTHyndFyVx7T8GKSzxPE", + "ad": "
", + "requestId": "dd2420bd-cdc2-4c66-8479-f3499ece73da", + "responseTimestamp": 1473983655565, + "requestTimestamp": 1473983655458, + "bidder": "sharethrough", + "adUnitCode": "div-gpt-ad-1460505748561-0", + "timeToRespond": 107, + "pbLg": "3.50", + "pbMg": "3.90", + "pbHg": "3.98", + "pbAg": "3.95", + "pbDg": "3.95", + "size": "600x300", + "adserverTargeting": { + "hb_bidder": "sharethrough", + "hb_adid": "23fbe93a90c924", + "hb_pb": "3.90", + "hb_size": "600x300" + } + }; + + sharethroughAnalytics.bidWon(args); + + }); + + it('should fire lose beacon', () => { + sinon.assert.calledOnce(fireLoseBeaconStub); + }); + + }); + + }); + + describe('lose beacon is fired', () => { + + beforeEach(() => { + sandbox.stub(sharethroughAnalytics, 'fireBeacon'); + sharethroughAnalytics.fireLoseBeacon('someoneelse', 10.0, 'arid', 'losebeacontype'); + }); + + it('should call correct url', () => { + let winUrl = sharethroughAnalytics.fireBeacon.firstCall.args[0]; + expect(winUrl).to.contain(sharethroughAnalytics.STR_BEACON_HOST + 'winnerBidderCode=someoneelse&winnerCpm=10&arid=arid&type=losebeacontype&hbVersion=%24prebid.version%24&strVersion=0.1.0&hbSource=prebid&'); + }); + }); + +}); \ No newline at end of file From 3ec356fadbfb4d8f500999211537ac44df96aa03 Mon Sep 17 00:00:00 2001 From: Nate Cozi Date: Thu, 12 Jan 2017 11:08:46 -0800 Subject: [PATCH 09/27] enable postMessage listener for cross-domain iframe support (#885) Support SafeFrame / x-domain on prebid creatives --- integrationExamples/gpt/x-domain/creative.js | 56 ++++++++++++++++++ src/prebid.js | 5 ++ src/secure-creatives.js | 61 ++++++++++++++++++++ 3 files changed, 122 insertions(+) create mode 100644 integrationExamples/gpt/x-domain/creative.js create mode 100644 src/secure-creatives.js diff --git a/integrationExamples/gpt/x-domain/creative.js b/integrationExamples/gpt/x-domain/creative.js new file mode 100644 index 00000000000..787b67f928b --- /dev/null +++ b/integrationExamples/gpt/x-domain/creative.js @@ -0,0 +1,56 @@ +// this script can be returned by an ad server delivering a cross domain iframe, into which the +// creative will be rendered, e.g. DFP delivering a SafeFrame + +// set these domains as fits your environment and ensure matching protocols +// alternatively this can be passed as a macro on the query string of the ad server call, for +// example `%%PUBLISHER_DOMAIN%%`. +const publisherDomain = 'http://localhost:9999'; +const adServerDomain = 'http://tpc.googlesyndication.com'; + +function renderAd(ev) { + var key = ev.message ? 'message' : 'data'; + var adObject = {}; + try { + adObject = JSON.parse(ev[key]); + } catch (e) { + return; + } + + if (adObject.ad || adObject.adUrl) { + var doc = window.document; + var ad = adObject.ad; + var url = adObject.adUrl; + var width = adObject.width; + var height = adObject.height; + + if (adObject.mediaType === 'video') { + console.log('Error trying to write ad.'); + } else + + if (ad) { + doc.write(ad); + doc.close(); + } else if (url) { + doc.write(''); + doc.close(); + } else { + console.log('Error trying to write ad. No ad for bid response id: ' + id); + } + } + } + +function requestAdFromPrebid() { + var message = JSON.stringify({ + message: 'Prebid Request', + adId: '%%PATTERN:hb_adid%%', + adServerDomain + }); + window.parent.postMessage(message, publisherDomain); +} + +function listenAdFromPrebid() { + window.addEventListener('message', renderAd, false); +} + +listenAdFromPrebid(); +requestAdFromPrebid(); diff --git a/src/prebid.js b/src/prebid.js index 2ba44911a65..1705c10b8d7 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -6,6 +6,7 @@ import { videoAdUnit, hasNonVideoBidder } from './video'; import 'polyfill'; import {parse as parseURL, format as formatURL} from './url'; import {isValidePriceConfig} from './cpmBucketManager'; +import {listenMessagesFromCreative} from './secure-creatives'; var $$PREBID_GLOBAL$$ = getGlobal(); var CONSTANTS = require('./constants.json'); @@ -55,6 +56,9 @@ $$PREBID_GLOBAL$$.timeoutBuffer = 200; $$PREBID_GLOBAL$$.logging = $$PREBID_GLOBAL$$.logging || false; +// domain where prebid is running for cross domain iframe communication +$$PREBID_GLOBAL$$.publisherDomain = $$PREBID_GLOBAL$$.publisherDomain || window.location.origin; + //let the world know we are loaded $$PREBID_GLOBAL$$.libLoaded = true; @@ -836,4 +840,5 @@ $$PREBID_GLOBAL$$.getHighestCpmBids = function (adUnitCode) { return getWinningBids(adUnitCode); }; +$$PREBID_GLOBAL$$.que.push(() => listenMessagesFromCreative()); processQue(); diff --git a/src/secure-creatives.js b/src/secure-creatives.js new file mode 100644 index 00000000000..e8f12ae860d --- /dev/null +++ b/src/secure-creatives.js @@ -0,0 +1,61 @@ +/* Secure Creatives + Provides support for rendering creatives into cross domain iframes such as SafeFrame to prevent + access to a publisher page from creative payloads. + */ + +import events from './events'; +import { EVENTS } from './constants'; + +const BID_WON = EVENTS.BID_WON; + + +export function listenMessagesFromCreative() { + addEventListener('message', receiveMessage, false); +} + +function receiveMessage(ev) { + var key = ev.message ? 'message' : 'data'; + var data = {}; + try { + data = JSON.parse(ev[key]); + } catch (e) { + return; + } + + if (data.adId) { + const adObject = $$PREBID_GLOBAL$$._bidsReceived.find(function (bid) { + return bid.adId === data.adId; + }); + + if (data.message === 'Prebid Request') { + sendAdToCreative(adObject, data.adServerDomain, ev.source); + events.emit(BID_WON, adObject); + } + } +} + +function sendAdToCreative(adObject, remoteDomain, source) { + const { adId, ad, adUrl, width, height } = adObject; + + if (adId) { + resizeRemoteCreative(adObject); + source.postMessage(JSON.stringify({ + message: 'Prebid Response', + ad, + adUrl, + width, + height + }), remoteDomain); + } +} + +function resizeRemoteCreative({ adUnitCode, width, height }) { + const iframe = document.getElementById(window.googletag.pubads() + .getSlots().find(slot => { + return slot.getAdUnitPath() === adUnitCode || + slot.getSlotElementId() === adUnitCode; + }).getSlotElementId()).querySelector('iframe'); + + iframe.width = '' + width; + iframe.height = '' + height; +} From 8ecfdb2b4a7bf142fbf5b2e258b51b3c2aa14390 Mon Sep 17 00:00:00 2001 From: Alex Stanovsky Date: Fri, 13 Jan 2017 00:05:05 +0200 Subject: [PATCH 10/27] Add pixel size (#892) --- src/adapters/memeglobal.js | 2 +- test/spec/adapters/memeglobal_spec.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/adapters/memeglobal.js b/src/adapters/memeglobal.js index d4de170bc1f..82b6ad4cf11 100644 --- a/src/adapters/memeglobal.js +++ b/src/adapters/memeglobal.js @@ -106,7 +106,7 @@ var MemeGlobalAdapter = function MemeGlobalAdapter() { bidResponse.placementCode = placementCode; bidResponse.size = bidRequested.sizes; var responseAd = bidderBid.adm; - var responseNurl = ''; + var responseNurl = ''; bidResponse.creative_id = bidderBid.id; bidResponse.bidderCode = bidderName; bidResponse.cpm = responseCPM; diff --git a/test/spec/adapters/memeglobal_spec.js b/test/spec/adapters/memeglobal_spec.js index 2490a4561e0..7221cb92984 100644 --- a/test/spec/adapters/memeglobal_spec.js +++ b/test/spec/adapters/memeglobal_spec.js @@ -161,7 +161,7 @@ describe('memeglobal adapter tests', function () { expect(bidObject1.cpm).to.equal(0.09); expect(bidObject1.height).to.equal(250); expect(bidObject1.width).to.equal(300); - expect(bidObject1.ad).to.equal('ad-code'); + expect(bidObject1.ad).to.equal('ad-code'); stubAddBidResponse.calledThrice; From 5358744a55dd4bcb3e675f8cc447b79d399a8738 Mon Sep 17 00:00:00 2001 From: chaac Date: Fri, 13 Jan 2017 15:57:29 -0700 Subject: [PATCH 11/27] GumGum adapter - include the bid timeout as `tmax` (#908) --- src/adapters/gumgum.js | 5 ++--- test/spec/adapters/gumgum_spec.js | 5 +++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/adapters/gumgum.js b/src/adapters/gumgum.js index 551a49042f4..7434444d570 100644 --- a/src/adapters/gumgum.js +++ b/src/adapters/gumgum.js @@ -18,8 +18,7 @@ const GumgumAdapter = function GumgumAdapter() { topWindow = global.top; topScreen = topWindow.screen; } catch (error) { - utils.logError(error); - return; + return utils.logError(error); } function _callBids({ bids }) { @@ -40,7 +39,7 @@ const GumgumAdapter = function GumgumAdapter() { const trackingId = params.inScreen; const nativeId = params.native; const slotId = params.inSlot; - const bid = {}; + const bid = { tmax: $$PREBID_GLOBAL$$.cbTimeout }; /* slot/native ads need the placement id */ switch (true) { diff --git a/test/spec/adapters/gumgum_spec.js b/test/spec/adapters/gumgum_spec.js index fe48ae3a3f4..58958373f94 100644 --- a/test/spec/adapters/gumgum_spec.js +++ b/test/spec/adapters/gumgum_spec.js @@ -114,6 +114,11 @@ describe('gumgum adapter', () => { endpointRequest.to.include('sh'); }); + it('should include the global bid timeout', () => { + const endpointRequest = expect(adLoader.loadScript.firstCall.args[0]); + endpointRequest.to.include(`tmax=${$$PREBID_GLOBAL$$.cbTimeout}`); + }); + it('should include the publisher identity', () => { const endpointRequest = expect(adLoader.loadScript.firstCall.args[0]); endpointRequest.to.include('t=' + TEST.PUBLISHER_IDENTITY); From 32fcc76ab60394d1c52191230d61174a234d6af2 Mon Sep 17 00:00:00 2001 From: jdelhommeau Date: Mon, 16 Jan 2017 18:25:27 +0100 Subject: [PATCH 12/27] Adding support for all AST parameters (#923) * Adding support for all AST parameters * adding braces on if * changing memberId into member to match AST doc --- src/adapters/appnexusAst.js | 42 +++++++++++++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/src/adapters/appnexusAst.js b/src/adapters/appnexusAst.js index 6960eee2fe2..67cc393bba7 100644 --- a/src/adapters/appnexusAst.js +++ b/src/adapters/appnexusAst.js @@ -26,6 +26,7 @@ function AppnexusAstAdapter() { /* Prebid executes this function when the page asks to send out bid requests */ baseAdapter.callBids = function(bidRequest) { const bids = bidRequest.bids || []; + var member = 0; const tags = bids .filter(bid => valid(bid)) .map(bid => { @@ -36,10 +37,39 @@ function AppnexusAstAdapter() { tag.sizes = getSizes(bid.sizes); tag.primary_size = tag.sizes[0]; tag.uuid = bid.bidId; - tag.id = parseInt(bid.params.placementId, 10); + if(bid.params.placementId) { + tag.id = parseInt(bid.params.placementId, 10); + } else { + tag.code = bid.params.invCode; + } tag.allow_smaller_sizes = bid.params.allowSmallerSizes || false; tag.prebid = true; tag.disable_psa = true; + member = parseInt(bid.params.member, 10); + if (bid.params.reserve) { + tag.reserve = bid.params.reserve; + } + if (bid.params.position) { + tag.position = {'above': 1, 'below': 2}[bid.params.position] || 0; + } + if (bid.params.trafficSourceCode) { + tag.traffic_source_code = bid.params.trafficSourceCode; + } + if (bid.params.privateSizes) { + tag.private_sizes = getSizes(bid.params.privateSizes); + } + if (bid.params.supplyType) { + tag.supply_type = bid.params.supplyType; + } + if (bid.params.pubClick) { + tag.pubclick = bid.params.pubClick; + } + if (bid.params.extInvCode) { + tag.ext_inv_code = bid.params.extInvCode; + } + if (bid.params.externalImpId) { + tag.external_imp_id = bid.params.externalImpId; + } if (!utils.isEmpty(bid.params.keywords)) { tag.keywords = getKeywords(bid.params.keywords); } @@ -64,7 +94,11 @@ function AppnexusAstAdapter() { }); if (!utils.isEmpty(tags)) { - const payload = JSON.stringify({tags: [...tags]}); + const payloadJson = {tags: [...tags]}; + if (member > 0) { + payloadJson.member_id = member; + } + const payload = JSON.stringify(payloadJson); ajax(ENDPOINT, handleResponse, payload, { contentType: 'text/plain', withCredentials : true @@ -133,10 +167,10 @@ function AppnexusAstAdapter() { /* Check that a bid has required paramters */ function valid(bid) { - if (bid.params.placementId || bid.params.memberId && bid.params.invCode) { + if (bid.params.placementId || bid.params.member && bid.params.invCode) { return bid; } else { - utils.logError('bid requires placementId or (memberId and invCode) params'); + utils.logError('bid requires placementId or (member and invCode) params'); } } From 1f40e9645d671cd5bc3695a7ee31fbd0a5630d6d Mon Sep 17 00:00:00 2001 From: jstocker76 Date: Tue, 17 Jan 2017 09:18:53 -0800 Subject: [PATCH 13/27] There are 2 changes- (#913) lines 53 and 61 - allows the http response from our ad api to set cookies. This somehow increases fill. everything else - fixes an issue where "no bid" responses aren't registered with prebid in certain cases, causing auction timeouts. --- src/adapters/rhythmone.js | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/adapters/rhythmone.js b/src/adapters/rhythmone.js index 46f3ffb0160..b86d3aee4d3 100644 --- a/src/adapters/rhythmone.js +++ b/src/adapters/rhythmone.js @@ -50,7 +50,7 @@ module.exports = function(bidManager, global, loader){ callback(200, "success", response.responseText); else callback(-1, "http error "+response.status, response.responseText); - }, false, {method:"GET"}); + }, false, {method:"GET", withCredentials: true}); } else{ loader(url, function(responseText, response){ @@ -58,7 +58,7 @@ module.exports = function(bidManager, global, loader){ callback(200, "success", response.responseText); else callback(-1, "http error "+response.status, response.responseText); - }, postData, {method:"POST", contentType: "application/json"}); + }, postData, {method:"POST", contentType: "application/json", withCredentials: true}); } } @@ -115,6 +115,7 @@ module.exports = function(bidManager, global, loader){ function noBids(params){ for(var i=0; i Date: Wed, 18 Jan 2017 17:15:13 -0500 Subject: [PATCH 14/27] Appnexus targeting function (#920) * WIP * Targeting code moved to separate file and setTargeting method for appnexus * adding missing file * Code refactoring after review --- src/prebid.js | 182 +++---------------------------- src/targeting.js | 188 ++++++++++++++++++++++++++++++++ test/spec/unit/pbjs_api_spec.js | 56 ++++++++++ 3 files changed, 260 insertions(+), 166 deletions(-) create mode 100644 src/targeting.js diff --git a/src/prebid.js b/src/prebid.js index 1705c10b8d7..def4e2ff738 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -1,7 +1,7 @@ /** @module $$PREBID_GLOBAL$$ */ import { getGlobal } from './prebidGlobal'; -import {flatten, uniques, isGptPubadsDefined, getHighestCpm, adUnitsFilter} from './utils'; +import {flatten, uniques, isGptPubadsDefined, adUnitsFilter} from './utils'; import { videoAdUnit, hasNonVideoBidder } from './video'; import 'polyfill'; import {parse as parseURL, format as formatURL} from './url'; @@ -17,6 +17,7 @@ var bidfactory = require('./bidfactory'); var adloader = require('./adloader'); var events = require('./events'); var adserver = require('./adserver.js'); +var targeting = require('./targeting.js'); /* private variables */ @@ -27,7 +28,6 @@ var BID_WON = CONSTANTS.EVENTS.BID_WON; var auctionRunning = false; var bidRequestQueue = []; -var pbTargetingKeys = []; var eventValidators = { bidWon: checkDefinedPlacement @@ -113,166 +113,6 @@ function checkDefinedPlacement(id) { return true; } -function resetPresetTargeting() { - if (isGptPubadsDefined()) { - window.googletag.pubads().getSlots().forEach(slot => { - pbTargetingKeys.forEach(function(key){ - slot.setTargeting(key,null); - }); - }); - } -} - -function setTargeting(targetingConfig) { - window.googletag.pubads().getSlots().forEach(slot => { - targetingConfig.filter(targeting => Object.keys(targeting)[0] === slot.getAdUnitPath() || - Object.keys(targeting)[0] === slot.getSlotElementId()) - .forEach(targeting => targeting[Object.keys(targeting)[0]] - .forEach(key => { - key[Object.keys(key)[0]] - .map((value) => { - utils.logMessage(`Attempting to set key value for slot: ${slot.getSlotElementId()} key: ${Object.keys(key)[0]} value: ${value}`); - return value; - }) - .forEach(value => { - slot.setTargeting(Object.keys(key)[0], value); - }); - })); - }); -} - -function getStandardKeys() { - return bidmanager.getStandardBidderAdServerTargeting() // in case using a custom standard key set - .map(targeting => targeting.key) - .concat(CONSTANTS.TARGETING_KEYS).filter(uniques); // standard keys defined in the library. -} - -function getWinningBids(adUnitCode) { - // use the given adUnitCode as a filter if present or all adUnitCodes if not - const adUnitCodes = adUnitCode ? - [adUnitCode] : - $$PREBID_GLOBAL$$._adUnitCodes; - - return $$PREBID_GLOBAL$$._bidsReceived - .filter(bid => adUnitCodes.includes(bid.adUnitCode)) - .filter(bid => bid.cpm > 0) - .map(bid => bid.adUnitCode) - .filter(uniques) - .map(adUnitCode => $$PREBID_GLOBAL$$._bidsReceived - .filter(bid => bid.adUnitCode === adUnitCode ? bid : null) - .reduce(getHighestCpm, - { - adUnitCode: adUnitCode, - cpm: 0, - adserverTargeting: {}, - timeToRespond: 0 - })); -} - -function getWinningBidTargeting() { - let winners = getWinningBids(); - - // winning bids with deals need an hb_deal targeting key - winners - .filter(bid => bid.dealId) - .map(bid => bid.adserverTargeting.hb_deal = bid.dealId); - - let standardKeys = getStandardKeys(); - winners = winners.map(winner => { - return { - [winner.adUnitCode]: Object.keys(winner.adserverTargeting) - .filter(key => - typeof winner.sendStandardTargeting === "undefined" || - winner.sendStandardTargeting || - standardKeys.indexOf(key) === -1) - .map(key => ({ [key.substring(0, 20)]: [winner.adserverTargeting[key]] })) - }; - }); - - return winners; -} - -function getDealTargeting() { - return $$PREBID_GLOBAL$$._bidsReceived.filter(bid => bid.dealId).map(bid => { - const dealKey = `hb_deal_${bid.bidderCode}`; - return { - [bid.adUnitCode]: getTargetingMap(bid, CONSTANTS.TARGETING_KEYS) - .concat({ [dealKey.substring(0, 20)]: [bid.adserverTargeting[dealKey]] }) - }; - }); -} - -/** - * Get custom targeting keys for bids that have `alwaysUseBid=true`. - */ -function getAlwaysUseBidTargeting(adUnitCodes) { - let standardKeys = getStandardKeys(); - return $$PREBID_GLOBAL$$._bidsReceived - .filter(adUnitsFilter.bind(this, adUnitCodes)) - .map(bid => { - if (bid.alwaysUseBid) { - return { - [bid.adUnitCode]: Object.keys(bid.adserverTargeting).map(key => { - // Get only the non-standard keys of the losing bids, since we - // don't want to override the standard keys of the winning bid. - if (standardKeys.indexOf(key) > -1) { - return; - } - - return { [key.substring(0, 20)]: [bid.adserverTargeting[key]] }; - - }).filter(key => key) // remove empty elements - }; - } - }) - .filter(bid => bid); // removes empty elements in array; -} - -function getBidLandscapeTargeting(adUnitCodes) { - const standardKeys = CONSTANTS.TARGETING_KEYS; - - return $$PREBID_GLOBAL$$._bidsReceived - .filter(adUnitsFilter.bind(this, adUnitCodes)) - .map(bid => { - if (bid.adserverTargeting) { - return { - [bid.adUnitCode]: getTargetingMap(bid, standardKeys) - }; - } - }).filter(bid => bid); // removes empty elements in array -} - -function getTargetingMap(bid, keys) { - return keys.map(key => { - return { - [`${key}_${bid.bidderCode}`.substring(0, 20)]: [bid.adserverTargeting[key]] - }; - }); -} - -function getAllTargeting(adUnitCode) { - const adUnitCodes = adUnitCode && adUnitCode.length ? [adUnitCode] : $$PREBID_GLOBAL$$._adUnitCodes; - - // Get targeting for the winning bid. Add targeting for any bids that have - // `alwaysUseBid=true`. If sending all bids is enabled, add targeting for losing bids. - var targeting = getWinningBidTargeting(adUnitCodes) - .concat(getAlwaysUseBidTargeting(adUnitCodes)) - .concat($$PREBID_GLOBAL$$._sendAllBids ? getBidLandscapeTargeting(adUnitCodes) : []) - .concat(getDealTargeting(adUnitCodes)); - - //store a reference of the targeting keys - targeting.map(adUnitCode => { - Object.keys(adUnitCode).map(key => { - adUnitCode[key].map(targetKey => { - if (pbTargetingKeys.indexOf(Object.keys(targetKey)[0]) === -1) { - pbTargetingKeys = Object.keys(targetKey).concat(pbTargetingKeys); - } - }); - }); - }); - return targeting; -} - /** * When a request for bids is made any stale bids remaining will be cleared for * a placement included in the outgoing bid request. @@ -333,7 +173,7 @@ $$PREBID_GLOBAL$$.getAdserverTargetingForAdUnitCode = function(adUnitCode) { $$PREBID_GLOBAL$$.getAdserverTargeting = function (adUnitCode) { utils.logInfo('Invoking $$PREBID_GLOBAL$$.getAdserverTargeting', arguments); - return getAllTargeting(adUnitCode) + return targeting.getAllTargeting(adUnitCode) .map(targeting => { return { [Object.keys(targeting)[0]]: targeting[Object.keys(targeting)[0]] @@ -403,9 +243,19 @@ $$PREBID_GLOBAL$$.setTargetingForGPTAsync = function () { } //first reset any old targeting - resetPresetTargeting(); + targeting.resetPresetTargeting(); //now set new targeting keys - setTargeting(getAllTargeting()); + targeting.setTargeting(targeting.getAllTargeting()); +}; + +$$PREBID_GLOBAL$$.setTargetingForAst = function() { + utils.logInfo('Invoking $$PREBID_GLOBAL$$.setTargetingForAn', arguments); + if(!targeting.isApntagDefined()) { + utils.logError('window.apntag is not defined on the page'); + return; + } + + targeting.setTargetingForAst(); }; /** @@ -837,7 +687,7 @@ $$PREBID_GLOBAL$$.setBidderSequence = function (order) { * @return {array} array containing highest cpm bid object(s) */ $$PREBID_GLOBAL$$.getHighestCpmBids = function (adUnitCode) { - return getWinningBids(adUnitCode); + return targeting.getWinningBids(adUnitCode); }; $$PREBID_GLOBAL$$.que.push(() => listenMessagesFromCreative()); diff --git a/src/targeting.js b/src/targeting.js new file mode 100644 index 00000000000..35c25224ce4 --- /dev/null +++ b/src/targeting.js @@ -0,0 +1,188 @@ +import { uniques, isGptPubadsDefined, getHighestCpm, adUnitsFilter } from './utils'; +const bidmanager = require('./bidmanager.js'); +const utils = require('./utils.js'); +var CONSTANTS = require('./constants.json'); + +var targeting = exports; +var pbTargetingKeys = []; + +targeting.resetPresetTargeting = function() { + if (isGptPubadsDefined()) { + window.googletag.pubads().getSlots().forEach(slot => { + pbTargetingKeys.forEach(function(key){ + slot.setTargeting(key,null); + }); + }); + } +}; + +targeting.getAllTargeting = function(adUnitCode) { + const adUnitCodes = adUnitCode && adUnitCode.length ? [adUnitCode] : $$PREBID_GLOBAL$$._adUnitCodes; + + // Get targeting for the winning bid. Add targeting for any bids that have + // `alwaysUseBid=true`. If sending all bids is enabled, add targeting for losing bids. + var targeting = getWinningBidTargeting(adUnitCodes) + .concat(getAlwaysUseBidTargeting(adUnitCodes)) + .concat($$PREBID_GLOBAL$$._sendAllBids ? getBidLandscapeTargeting(adUnitCodes) : []) + .concat(getDealTargeting(adUnitCodes)); + + //store a reference of the targeting keys + targeting.map(adUnitCode => { + Object.keys(adUnitCode).map(key => { + adUnitCode[key].map(targetKey => { + if (pbTargetingKeys.indexOf(Object.keys(targetKey)[0]) === -1) { + pbTargetingKeys = Object.keys(targetKey).concat(pbTargetingKeys); + } + }); + }); + }); + return targeting; +}; + +targeting.setTargeting = function(targetingConfig) { + window.googletag.pubads().getSlots().forEach(slot => { + targetingConfig.filter(targeting => Object.keys(targeting)[0] === slot.getAdUnitPath() || + Object.keys(targeting)[0] === slot.getSlotElementId()) + .forEach(targeting => targeting[Object.keys(targeting)[0]] + .forEach(key => { + key[Object.keys(key)[0]] + .map((value) => { + utils.logMessage(`Attempting to set key value for slot: ${slot.getSlotElementId()} key: ${Object.keys(key)[0]} value: ${value}`); + return value; + }) + .forEach(value => { + slot.setTargeting(Object.keys(key)[0], value); + }); + })); + }); +}; + +targeting.getWinningBids = function(adUnitCode) { + // use the given adUnitCode as a filter if present or all adUnitCodes if not + const adUnitCodes = adUnitCode ? [adUnitCode] : $$PREBID_GLOBAL$$._adUnitCodes; + + return $$PREBID_GLOBAL$$._bidsReceived + .filter(bid => adUnitCodes.includes(bid.adUnitCode)) + .filter(bid => bid.cpm > 0) + .map(bid => bid.adUnitCode) + .filter(uniques) + .map(adUnitCode => $$PREBID_GLOBAL$$._bidsReceived + .filter(bid => bid.adUnitCode === adUnitCode ? bid : null) + .reduce(getHighestCpm, + { + adUnitCode: adUnitCode, + cpm: 0, + adserverTargeting: {}, + timeToRespond: 0 + })); +}; + +targeting.setTargetingForAst = function() { + let targeting = $$PREBID_GLOBAL$$.getAdserverTargeting(); + Object.keys(targeting).forEach(targetId => + Object.keys(targeting[targetId]).forEach(key => { + utils.logMessage(`Attempting to set targeting for targetId: ${targetId} key: ${key} value: ${targeting[targetId][key]}`); + //setKeywords supports string and array as value + if(utils.isStr(targeting[targetId][key]) || utils.isArray(targeting[targetId][key])) { + var keywordsObj = {}; + var nKey = (key === 'hb_adid') ? key.toUpperCase() : key; + keywordsObj[nKey] = targeting[targetId][key]; + window.apntag.setKeywords(targetId,keywordsObj); + } + }) + ); +}; + +function getWinningBidTargeting() { + let winners = targeting.getWinningBids(); + + // winning bids with deals need an hb_deal targeting key + // adding hb_deal to bid.adserverTargeting if it exists in winners array + winners + .filter(bid => bid.dealId) + .map(bid => bid.adserverTargeting.hb_deal = bid.dealId); + + let standardKeys = getStandardKeys(); + winners = winners.map(winner => { + return { + [winner.adUnitCode]: Object.keys(winner.adserverTargeting) + .filter(key => + typeof winner.sendStandardTargeting === "undefined" || + winner.sendStandardTargeting || + standardKeys.indexOf(key) === -1) + .map(key => ({ [key.substring(0, 20)]: [winner.adserverTargeting[key]] })) + }; + }); + + return winners; +} + +function getStandardKeys() { + return bidmanager.getStandardBidderAdServerTargeting() // in case using a custom standard key set + .map(targeting => targeting.key) + .concat(CONSTANTS.TARGETING_KEYS).filter(uniques); // standard keys defined in the library. +} + +/** + * Get custom targeting keys for bids that have `alwaysUseBid=true`. + */ +function getAlwaysUseBidTargeting(adUnitCodes) { + let standardKeys = getStandardKeys(); + return $$PREBID_GLOBAL$$._bidsReceived + .filter(adUnitsFilter.bind(this, adUnitCodes)) + .map(bid => { + if (bid.alwaysUseBid) { + return { + [bid.adUnitCode]: Object.keys(bid.adserverTargeting).map(key => { + // Get only the non-standard keys of the losing bids, since we + // don't want to override the standard keys of the winning bid. + if (standardKeys.indexOf(key) > -1) { + return; + } + + return { [key.substring(0, 20)]: [bid.adserverTargeting[key]] }; + + }).filter(key => key) // remove empty elements + }; + } + }) + .filter(bid => bid); // removes empty elements in array; +} + +function getBidLandscapeTargeting(adUnitCodes) { + const standardKeys = CONSTANTS.TARGETING_KEYS; + + return $$PREBID_GLOBAL$$._bidsReceived + .filter(adUnitsFilter.bind(this, adUnitCodes)) + .map(bid => { + if (bid.adserverTargeting) { + return { + [bid.adUnitCode]: getTargetingMap(bid, standardKeys) + }; + } + }).filter(bid => bid); // removes empty elements in array +} + +function getTargetingMap(bid, keys) { + return keys.map(key => { + return { + [`${key}_${bid.bidderCode}`.substring(0, 20)]: [bid.adserverTargeting[key]] + }; + }); +} + +function getDealTargeting() { + return $$PREBID_GLOBAL$$._bidsReceived.filter(bid => bid.dealId).map(bid => { + const dealKey = `hb_deal_${bid.bidderCode}`; + return { + [bid.adUnitCode]: getTargetingMap(bid, CONSTANTS.TARGETING_KEYS) + .concat({ [dealKey.substring(0, 20)]: [bid.adserverTargeting[dealKey]] }) + }; + }); +} + +targeting.isApntagDefined = function() { + if (window.apntag && utils.isFn(window.apntag.setKeywords)) { + return true; + } +}; diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index e1fc7e29bc4..74a276929fa 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -106,6 +106,35 @@ window.googletag = { } }; +var createTagAST = function() { + var tags = {}; + tags[config.adUnitCodes[0]] = { + keywords : {} + }; + return tags; +}; + +window.apntag = { + keywords: [], + tags : createTagAST(), + setKeywords: function(key, params) { + var self = this; + if(!self.tags.hasOwnProperty(key)) { + return; + } + self.tags[key].keywords = this.tags[key].keywords || {}; + + utils._each(params,function(param,id){ + if (!self.tags[key].keywords.hasOwnProperty(id)) + self.tags[key].keywords[id] = param; + else if (!utils.isArray(self.tags[key].keywords[id])) + self.tags[key].keywords[id] = [self.tags[key].keywords[id]].concat(param); + else + self.tags[key].keywords[id] = self.tags[key].keywords[id].concat(param); + }); + } +}; + describe('Unit: Prebid Module', function () { after(function(){ $$PREBID_GLOBAL$$.adUnits = []; @@ -1476,4 +1505,31 @@ describe('Unit: Prebid Module', function () { }); }); + describe('setTargetingForAst', () => { + beforeEach(() => { + resetAuction(); + }); + + afterEach(() => { + resetAuction(); + }); + + it('should set targeting for appnexus apntag object', () => { + const adUnitCode = '/19968336/header-bid-tag-0'; + const bidder = 'appnexus'; + const bids = $$PREBID_GLOBAL$$._bidsReceived.filter(bid => (bid.adUnitCode === adUnitCode && bid.bidderCode === bidder)); + + var expectedAdserverTargeting = bids[0].adserverTargeting; + var newAdserverTargeting = {}; + for(var key in expectedAdserverTargeting) { + var nkey = (key === 'hb_adid') ? key.toUpperCase() : key; + newAdserverTargeting[nkey] = expectedAdserverTargeting[key]; + } + + $$PREBID_GLOBAL$$.setTargetingForAst(); + expect(newAdserverTargeting).to.deep.equal(window.apntag.tags[adUnitCode].keywords); + }); + + }); + }); From dc350e2a5cf9479833fd18cc486cc06f86627931 Mon Sep 17 00:00:00 2001 From: Vladislav Isaiko Date: Thu, 19 Jan 2017 00:16:12 +0200 Subject: [PATCH 15/27] Smartyads Adapter (#895) * add adapter smartyads * change domain * Add tests * rename test * bag-fix test * Trigger for current build rerun on Travis CI --- adapters.json | 1 + integrationExamples/gpt/pbjs_example_gpt.html | 8 +- src/adapters/smartyads.js | 191 ++++++++++++++++++ test/spec/adapters/smartyads_spec.js | 131 ++++++++++++ 4 files changed, 330 insertions(+), 1 deletion(-) create mode 100755 src/adapters/smartyads.js create mode 100644 test/spec/adapters/smartyads_spec.js diff --git a/adapters.json b/adapters.json index e4fd2afe9fd..e373420be71 100644 --- a/adapters.json +++ b/adapters.json @@ -24,6 +24,7 @@ "pulsepoint", "rhythmone", "rubicon", + "smartyads", "smartadserver", "sekindoUM", "sonobi", diff --git a/integrationExamples/gpt/pbjs_example_gpt.html b/integrationExamples/gpt/pbjs_example_gpt.html index 06a4afa5d47..cf723708672 100644 --- a/integrationExamples/gpt/pbjs_example_gpt.html +++ b/integrationExamples/gpt/pbjs_example_gpt.html @@ -225,11 +225,17 @@ } }, { - bidder: 'widespace', + bidder: 'widespace', params: { sid: '7b6589bf-95c8-4656-90b9-af9737bb9ad3', cur: 'EUR' } + }, + { + bidder: 'smartyads', + params: { + banner_id: 0 + } } ] }, { diff --git a/src/adapters/smartyads.js b/src/adapters/smartyads.js new file mode 100755 index 00000000000..9f6bedf5243 --- /dev/null +++ b/src/adapters/smartyads.js @@ -0,0 +1,191 @@ +import * as Adapter from './adapter.js'; +import bidfactory from 'src/bidfactory'; +import bidmanager from 'src/bidmanager'; +import * as utils from 'src/utils'; +import {ajax} from 'src/ajax'; +import {STATUS} from 'src/constants'; + +const SMARTYADS_BIDDER_CODE = 'smartyads'; + +var sizeMap = { + 1: '468x60', + 2: '728x90', + 8: '120x600', + 9: '160x600', + 10: '300x600', + 15: '300x250', + 16: '336x280', + 19: '300x100', + 43: '320x50', + 44: '300x50', + 48: '300x300', + 54: '300x1050', + 55: '970x90', + 57: '970x250', + 58: '1000x90', + 59: '320x80', + 61: '1000x1000', + 65: '640x480', + 67: '320x480', + 68: '1800x1000', + 72: '320x320', + 73: '320x160', + 83: '480x300', + 94: '970x310', + 96: '970x210', + 101: '480x320', + 102: '768x1024', + 113: '1000x300', + 117: '320x100', + 125: '800x250', + 126: '200x600' +}; + +utils._each(sizeMap, (item, key) => sizeMap[item] = key); + +function SmartyadsAdapter() { + + + function _callBids(bidderRequest) { + + var bids = bidderRequest.bids || []; + + bids.forEach((bid) => { + try { + ajax(buildOptimizedCall(bid), bidCallback, undefined, {withCredentials: true}); + } catch (err) { + utils.logError('Error sending smartyads request for placement code ' + bid.placementCode, null, err); + } + + function bidCallback(responseText) { + + try { + utils.logMessage('XHR callback function called for ad ID: ' + bid.bidId); + handleRpCB(responseText, bid); + } catch (err) { + if (typeof err === "string") { + utils.logWarn(`${err} when processing smartyads response for placement code ${bid.placementCode}`); + } else { + utils.logError('Error processing smartyads response for placement code ' + bid.placementCode, null, err); + } + + //indicate that there is no bid for this placement + let badBid = bidfactory.createBid(STATUS.NO_BID, bid); + badBid.bidderCode = bid.bidder; + badBid.error = err; + bidmanager.addBidResponse(bid.placementCode, badBid); + } + } + }); + } + + + function buildOptimizedCall(bid) { + + bid.startTime = new Date().getTime(); + + // use smartyads sizes if provided, otherwise adUnit.sizes + var parsedSizes = SmartyadsAdapter.masSizeOrdering( + Array.isArray(bid.params.sizes) ? bid.params.sizes.map(size => (sizeMap[size] || '').split('x')) : bid.sizes + ); + + if (parsedSizes.length < 1) { + throw "no valid sizes"; + } + + var secure; + if (window.location.protocol !== 'http:') { + secure = 1; + } else { + secure = 0; + } + + var host = window.location.host, + page = window.location.pathname, + language = navigator.language, + deviceWidth = window.screen.width, + deviceHeight = window.screen.height; + + var queryString = [ + 'banner_id', bid.params.banner_id, + 'size_ad', parsedSizes[0], + 'alt_size_ad', parsedSizes.slice(1).join(',') || undefined, + 'host', host, + "page", page, + "language", language, + "deviceWidth", deviceWidth, + "deviceHeight", deviceHeight, + "secure", secure, + "bidId", bid.bidId, + "checkOn", 'rf' + ]; + + return queryString.reduce( + (memo, curr, index) => + index % 2 === 0 && queryString[index + 1] !== undefined ? + memo + curr + '=' + encodeURIComponent(queryString[index + 1]) + '&' + : memo, + '//ssp-nj.webtradehub.com/?' + ).slice(0, -1); + + } + + function handleRpCB(responseText, bidRequest) { + + let ad = JSON.parse(responseText); // can throw + + var bid = bidfactory.createBid(STATUS.GOOD, bidRequest); + bid.creative_id = ad.ad_id; + bid.bidderCode = bidRequest.bidder; + bid.cpm = ad.cpm || 0; + bid.ad = ad.adm; + bid.width = ad.width; + bid.height = ad.height; + bid.dealId = ad.deal; + + bidmanager.addBidResponse(bidRequest.placementCode, bid); + } + + return Object.assign(Adapter.createNew(SMARTYADS_BIDDER_CODE), { // SMARTYADS_BIDDER_CODE smartyads + callBids: _callBids, + createNew: SmartyadsAdapter.createNew + }); +} + +SmartyadsAdapter.masSizeOrdering = function (sizes) { + + const MAS_SIZE_PRIORITY = [15, 2, 9]; + + return utils.parseSizesInput(sizes) + // map sizes while excluding non-matches + .reduce((result, size) => { + let mappedSize = parseInt(sizeMap[size], 10); + if (mappedSize) { + result.push(mappedSize); + } + return result; + }, []) + .sort((first, second) => { + // sort by MAS_SIZE_PRIORITY priority order + let firstPriority = MAS_SIZE_PRIORITY.indexOf(first), + secondPriority = MAS_SIZE_PRIORITY.indexOf(second); + + if (firstPriority > -1 || secondPriority > -1) { + if (firstPriority === -1) { + return 1; + } + if (secondPriority === -1) { + return -1; + } + return firstPriority - secondPriority; + } + + return first - second; + }); +}; + +SmartyadsAdapter.createNew = function () { + return new SmartyadsAdapter(); +}; + +module.exports = SmartyadsAdapter; \ No newline at end of file diff --git a/test/spec/adapters/smartyads_spec.js b/test/spec/adapters/smartyads_spec.js new file mode 100644 index 00000000000..357599e69cd --- /dev/null +++ b/test/spec/adapters/smartyads_spec.js @@ -0,0 +1,131 @@ +import { expect } from 'chai'; +import Adapter from '../../../src/adapters/smartyads'; +import adapterManager from 'src/adaptermanager'; +import bidManager from 'src/bidmanager'; +import CONSTANTS from 'src/constants.json'; + +describe('Smartyads adapter tests', function () { + + let sandbox; + const adUnit = { // TODO CHANGE + code: 'smartyads', + sizes: [[300, 250], [300,600], [320, 80]], + bids: [{ + bidder: 'smartyads', + params: { + banner_id: 0 + } + }] + }; + + const response = { + ad_id: 0, + adm: "Test Response", + cpm: 0.5, + deal: "bf063e2e025c", + height: 240, + width: 360 + }; + + beforeEach(() => { + sandbox = sinon.sandbox.create(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + describe('Smartyads callBids validation', () => { + + let bids, + server; + + beforeEach(() => { + bids = []; + server = sinon.fakeServer.create(); + + sandbox.stub(bidManager, 'addBidResponse', (elemId, bid) => { + bids.push(bid); + }); + }); + + afterEach(() => { + server.restore(); + }); + + let adapter = adapterManager.bidderRegistry['smartyads']; + + it('Valid bid-request', () => { + sandbox.stub(adapter, 'callBids'); + adapterManager.callBids({ + adUnits: [clone(adUnit)] + }); + + let bidderRequest = adapter.callBids.getCall(0).args[0]; + + expect(bidderRequest).to.have.property('bids') + .that.is.an('array') + .with.lengthOf(1); + + expect(bidderRequest).to.have.deep.property('bids[0]') + .to.have.property('bidder', 'smartyads'); + + expect(bidderRequest).to.have.deep.property('bids[0]') + .with.property('sizes') + .that.is.an('array') + .with.lengthOf(3) + .that.deep.equals(adUnit.sizes); + expect(bidderRequest).to.have.deep.property('bids[0]') + .with.property('params') + .to.have.property('banner_id', 0); + }); + + it('Valid bid-response', ()=>{ + server.respondWith(JSON.stringify( + response + )); + adapterManager.callBids({ + adUnits: [clone(adUnit)] + }); + server.respond(); + + expect(bids).to.be.lengthOf(1); + expect(bids[0].getStatusCode()).to.equal(CONSTANTS.STATUS.GOOD); + expect(bids[0].bidderCode).to.equal("smartyads"); + expect(bids[0].width).to.equal(360); + expect(bids[0].height).to.equal(240); + expect(bids[0].cpm).to.equal(0.5); + expect(bids[0].dealId).to.equal("bf063e2e025c"); + }); + }); + + describe('MAS mapping / ordering', () => { + + let masSizeOrdering = Adapter.masSizeOrdering; + + it('should not include values without a proper mapping', () => { + let ordering = masSizeOrdering([[320, 50], [42, 42], [300, 250], [640, 480], [1, 1], [336, 280]]); + expect(ordering).to.deep.equal([15, 16, 43, 65]); + }); + + it('should sort values without any MAS priority sizes in regular ascending order', () => { + let ordering = masSizeOrdering([[320, 50], [640, 480], [336, 280], [200, 600]]); + expect(ordering).to.deep.equal([16, 43, 65, 126]); + }); + + it('should sort MAS priority sizes in the proper order w/ rest ascending', () => { + let ordering = masSizeOrdering([[320, 50], [160,600], [640, 480], [300, 250],[336, 280], [200, 600]]); + expect(ordering).to.deep.equal([15, 9, 16, 43, 65, 126]); + + ordering = masSizeOrdering([[320, 50], [300, 250], [160,600], [640, 480],[336, 280], [200, 600], [728, 90]]); + expect(ordering).to.deep.equal([15, 2, 9, 16, 43, 65, 126]); + + ordering = masSizeOrdering([[120, 600], [320, 50], [160,600], [640, 480],[336, 280], [200, 600], [728, 90]]); + expect(ordering).to.deep.equal([2, 9, 8, 16, 43, 65, 126]); + }) + }); +}); + +function clone(obj) { + return JSON.parse(JSON.stringify(obj)); +} \ No newline at end of file From c1aa4fb0fda7113401b4b8ac00d153dc0fcdb3ce Mon Sep 17 00:00:00 2001 From: Rich Snapp Date: Wed, 18 Jan 2017 15:37:30 -0700 Subject: [PATCH 16/27] Catch errors in bidsBackHandler. Also fix test cleanup in pbjs api spec. (#905) --- src/bidmanager.js | 3 +++ test/spec/unit/pbjs_api_spec.js | 40 ++++++++++++++++++++++++--------- 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/src/bidmanager.js b/src/bidmanager.js index 1d3a7ea47fe..ffd4b24632b 100644 --- a/src/bidmanager.js +++ b/src/bidmanager.js @@ -261,6 +261,9 @@ exports.executeCallback = function (timedOut) { try { processCallbacks([externalCallbacks.oneTime]); } + catch(e){ + utils.logError('Error executing bidsBackHandler', null, e); + } finally { externalCallbacks.oneTime = null; externalCallbacks.timer = false; diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index 74a276929fa..98538a5cd1a 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -602,6 +602,18 @@ describe('Unit: Prebid Module', function () { }); describe('requestBids', () => { + + var adUnitsBackup; + + beforeEach(() => { + adUnitsBackup = $$PREBID_GLOBAL$$.adUnits; + }); + + afterEach(() => { + $$PREBID_GLOBAL$$.adUnits = adUnitsBackup; + resetAuction(); + }); + it('should add bidsBackHandler callback to bidmanager', () => { var spyAddOneTimeCallBack = sinon.spy(bidmanager, 'addOneTimeCallback'); var requestObj = { @@ -612,20 +624,16 @@ describe('Unit: Prebid Module', function () { assert.ok(spyAddOneTimeCallBack.calledWith(requestObj.bidsBackHandler), 'called bidmanager.addOneTimeCallback'); bidmanager.addOneTimeCallback.restore(); - resetAuction(); }); it('should log message when adUnits not configured', () => { const logMessageSpy = sinon.spy(utils, 'logMessage'); - const adUnitsBackup = $$PREBID_GLOBAL$$.adUnits; $$PREBID_GLOBAL$$.adUnits = []; $$PREBID_GLOBAL$$.requestBids({}); assert.ok(logMessageSpy.calledWith('No adUnits configured. No bids requested.'), 'expected message was logged'); utils.logMessage.restore(); - $$PREBID_GLOBAL$$.adUnits = adUnitsBackup; - resetAuction(); }); it('should execute callback after timeout', () => { @@ -648,12 +656,10 @@ describe('Unit: Prebid Module', function () { bidmanager.executeCallback.restore(); clock.restore(); - resetAuction(); }); it('should execute callback immediately if adUnits is empty', () => { var spyExecuteCallback = sinon.spy(bidmanager, 'executeCallback'); - const adUnitsBackup = $$PREBID_GLOBAL$$.adUnits; $$PREBID_GLOBAL$$.adUnits = []; $$PREBID_GLOBAL$$.requestBids({}); @@ -662,16 +668,30 @@ describe('Unit: Prebid Module', function () { ' empty'); bidmanager.executeCallback.restore(); - $$PREBID_GLOBAL$$.adUnits = adUnitsBackup; - resetAuction(); + }); + + it('should not propagate exceptions from bidsBackHandler', () => { + $$PREBID_GLOBAL$$.adUnits = []; + + var requestObj = { + bidsBackHandler: function bidsBackHandlerCallback() { + var test = undefined; + return test.test; + } + }; + + expect(() => { + $$PREBID_GLOBAL$$.requestBids(requestObj); + }).not.to.throw(); + }); it('should call callBids function on adaptermanager', () => { var spyCallBids = sinon.spy(adaptermanager, 'callBids'); + $$PREBID_GLOBAL$$.requestBids({}); assert.ok(spyCallBids.called, 'called adaptermanager.callBids'); adaptermanager.callBids.restore(); - resetAuction(); }); it('should not callBids if a video adUnit has non-video bidders', () => { @@ -692,7 +712,6 @@ describe('Unit: Prebid Module', function () { adaptermanager.callBids.restore(); adaptermanager.videoAdapters = videoAdaptersBackup; - resetAuction(); }); it('should callBids if a video adUnit has all video bidders', () => { @@ -712,7 +731,6 @@ describe('Unit: Prebid Module', function () { adaptermanager.callBids.restore(); adaptermanager.videoAdapters = videoAdaptersBackup; - resetAuction(); }); it('should queue bid requests when a previous bid request is in process', () => { From 509272b029435dab743351a535ac4d265da05414 Mon Sep 17 00:00:00 2001 From: protonate Date: Wed, 18 Jan 2017 14:51:56 -0800 Subject: [PATCH 17/27] update code style - smartyads adapter --- src/adapters/smartyads.js | 312 +++++++++++++++++++------------------- 1 file changed, 155 insertions(+), 157 deletions(-) diff --git a/src/adapters/smartyads.js b/src/adapters/smartyads.js index 9f6bedf5243..2f2ce8c28ad 100755 --- a/src/adapters/smartyads.js +++ b/src/adapters/smartyads.js @@ -8,184 +8,182 @@ import {STATUS} from 'src/constants'; const SMARTYADS_BIDDER_CODE = 'smartyads'; var sizeMap = { - 1: '468x60', - 2: '728x90', - 8: '120x600', - 9: '160x600', - 10: '300x600', - 15: '300x250', - 16: '336x280', - 19: '300x100', - 43: '320x50', - 44: '300x50', - 48: '300x300', - 54: '300x1050', - 55: '970x90', - 57: '970x250', - 58: '1000x90', - 59: '320x80', - 61: '1000x1000', - 65: '640x480', - 67: '320x480', - 68: '1800x1000', - 72: '320x320', - 73: '320x160', - 83: '480x300', - 94: '970x310', - 96: '970x210', - 101: '480x320', - 102: '768x1024', - 113: '1000x300', - 117: '320x100', - 125: '800x250', - 126: '200x600' + 1: '468x60', + 2: '728x90', + 8: '120x600', + 9: '160x600', + 10: '300x600', + 15: '300x250', + 16: '336x280', + 19: '300x100', + 43: '320x50', + 44: '300x50', + 48: '300x300', + 54: '300x1050', + 55: '970x90', + 57: '970x250', + 58: '1000x90', + 59: '320x80', + 61: '1000x1000', + 65: '640x480', + 67: '320x480', + 68: '1800x1000', + 72: '320x320', + 73: '320x160', + 83: '480x300', + 94: '970x310', + 96: '970x210', + 101: '480x320', + 102: '768x1024', + 113: '1000x300', + 117: '320x100', + 125: '800x250', + 126: '200x600' }; utils._each(sizeMap, (item, key) => sizeMap[item] = key); function SmartyadsAdapter() { - - function _callBids(bidderRequest) { - - var bids = bidderRequest.bids || []; - - bids.forEach((bid) => { - try { - ajax(buildOptimizedCall(bid), bidCallback, undefined, {withCredentials: true}); - } catch (err) { - utils.logError('Error sending smartyads request for placement code ' + bid.placementCode, null, err); - } - - function bidCallback(responseText) { - - try { - utils.logMessage('XHR callback function called for ad ID: ' + bid.bidId); - handleRpCB(responseText, bid); - } catch (err) { - if (typeof err === "string") { - utils.logWarn(`${err} when processing smartyads response for placement code ${bid.placementCode}`); - } else { - utils.logError('Error processing smartyads response for placement code ' + bid.placementCode, null, err); - } - - //indicate that there is no bid for this placement - let badBid = bidfactory.createBid(STATUS.NO_BID, bid); - badBid.bidderCode = bid.bidder; - badBid.error = err; - bidmanager.addBidResponse(bid.placementCode, badBid); - } - } - }); - } - - - function buildOptimizedCall(bid) { - - bid.startTime = new Date().getTime(); - - // use smartyads sizes if provided, otherwise adUnit.sizes - var parsedSizes = SmartyadsAdapter.masSizeOrdering( - Array.isArray(bid.params.sizes) ? bid.params.sizes.map(size => (sizeMap[size] || '').split('x')) : bid.sizes - ); - - if (parsedSizes.length < 1) { - throw "no valid sizes"; - } - - var secure; - if (window.location.protocol !== 'http:') { - secure = 1; - } else { - secure = 0; + function _callBids(bidderRequest) { + + var bids = bidderRequest.bids || []; + + bids.forEach((bid) => { + try { + ajax(buildOptimizedCall(bid), bidCallback, undefined, { withCredentials: true }); + } catch (err) { + utils.logError('Error sending smartyads request for placement code ' + bid.placementCode, null, err); + } + + function bidCallback(responseText) { + + try { + utils.logMessage('XHR callback function called for ad ID: ' + bid.bidId); + handleRpCB(responseText, bid); + } catch (err) { + if (typeof err === "string") { + utils.logWarn(`${err} when processing smartyads response for placement code ${bid.placementCode}`); + } else { + utils.logError('Error processing smartyads response for placement code ' + bid.placementCode, null, err); + } + + //indicate that there is no bid for this placement + let badBid = bidfactory.createBid(STATUS.NO_BID, bid); + badBid.bidderCode = bid.bidder; + badBid.error = err; + bidmanager.addBidResponse(bid.placementCode, badBid); } + } + }); + } - var host = window.location.host, - page = window.location.pathname, - language = navigator.language, - deviceWidth = window.screen.width, - deviceHeight = window.screen.height; - - var queryString = [ - 'banner_id', bid.params.banner_id, - 'size_ad', parsedSizes[0], - 'alt_size_ad', parsedSizes.slice(1).join(',') || undefined, - 'host', host, - "page", page, - "language", language, - "deviceWidth", deviceWidth, - "deviceHeight", deviceHeight, - "secure", secure, - "bidId", bid.bidId, - "checkOn", 'rf' - ]; - - return queryString.reduce( - (memo, curr, index) => - index % 2 === 0 && queryString[index + 1] !== undefined ? - memo + curr + '=' + encodeURIComponent(queryString[index + 1]) + '&' - : memo, - '//ssp-nj.webtradehub.com/?' - ).slice(0, -1); - - } + function buildOptimizedCall(bid) { - function handleRpCB(responseText, bidRequest) { + bid.startTime = new Date().getTime(); - let ad = JSON.parse(responseText); // can throw + // use smartyads sizes if provided, otherwise adUnit.sizes + var parsedSizes = SmartyadsAdapter.masSizeOrdering( + Array.isArray(bid.params.sizes) ? bid.params.sizes.map(size => (sizeMap[size] || '').split('x')) : bid.sizes + ); - var bid = bidfactory.createBid(STATUS.GOOD, bidRequest); - bid.creative_id = ad.ad_id; - bid.bidderCode = bidRequest.bidder; - bid.cpm = ad.cpm || 0; - bid.ad = ad.adm; - bid.width = ad.width; - bid.height = ad.height; - bid.dealId = ad.deal; + if (parsedSizes.length < 1) { + throw "no valid sizes"; + } - bidmanager.addBidResponse(bidRequest.placementCode, bid); + var secure; + if (window.location.protocol !== 'http:') { + secure = 1; + } else { + secure = 0; } - return Object.assign(Adapter.createNew(SMARTYADS_BIDDER_CODE), { // SMARTYADS_BIDDER_CODE smartyads - callBids: _callBids, - createNew: SmartyadsAdapter.createNew - }); + var host = window.location.host, + page = window.location.pathname, + language = navigator.language, + deviceWidth = window.screen.width, + deviceHeight = window.screen.height; + + var queryString = [ + 'banner_id', bid.params.banner_id, + 'size_ad', parsedSizes[0], + 'alt_size_ad', parsedSizes.slice(1).join(',') || undefined, + 'host', host, + "page", page, + "language", language, + "deviceWidth", deviceWidth, + "deviceHeight", deviceHeight, + "secure", secure, + "bidId", bid.bidId, + "checkOn", 'rf' + ]; + + return queryString.reduce( + (memo, curr, index) => + index % 2 === 0 && queryString[index + 1] !== undefined ? + memo + curr + '=' + encodeURIComponent(queryString[index + 1]) + '&' + : memo, + '//ssp-nj.webtradehub.com/?' + ).slice(0, -1); + + } + + function handleRpCB(responseText, bidRequest) { + + let ad = JSON.parse(responseText); // can throw + + var bid = bidfactory.createBid(STATUS.GOOD, bidRequest); + bid.creative_id = ad.ad_id; + bid.bidderCode = bidRequest.bidder; + bid.cpm = ad.cpm || 0; + bid.ad = ad.adm; + bid.width = ad.width; + bid.height = ad.height; + bid.dealId = ad.deal; + + bidmanager.addBidResponse(bidRequest.placementCode, bid); + } + + return Object.assign(Adapter.createNew(SMARTYADS_BIDDER_CODE), { // SMARTYADS_BIDDER_CODE smartyads + callBids: _callBids, + createNew: SmartyadsAdapter.createNew + }); } SmartyadsAdapter.masSizeOrdering = function (sizes) { - const MAS_SIZE_PRIORITY = [15, 2, 9]; - - return utils.parseSizesInput(sizes) - // map sizes while excluding non-matches - .reduce((result, size) => { - let mappedSize = parseInt(sizeMap[size], 10); - if (mappedSize) { - result.push(mappedSize); - } - return result; - }, []) - .sort((first, second) => { - // sort by MAS_SIZE_PRIORITY priority order - let firstPriority = MAS_SIZE_PRIORITY.indexOf(first), - secondPriority = MAS_SIZE_PRIORITY.indexOf(second); - - if (firstPriority > -1 || secondPriority > -1) { - if (firstPriority === -1) { - return 1; - } - if (secondPriority === -1) { - return -1; - } - return firstPriority - secondPriority; - } - - return first - second; - }); + const MAS_SIZE_PRIORITY = [15, 2, 9]; + + return utils.parseSizesInput(sizes) + // map sizes while excluding non-matches + .reduce((result, size) => { + let mappedSize = parseInt(sizeMap[size], 10); + if (mappedSize) { + result.push(mappedSize); + } + return result; + }, []) + .sort((first, second) => { + // sort by MAS_SIZE_PRIORITY priority order + let firstPriority = MAS_SIZE_PRIORITY.indexOf(first), + secondPriority = MAS_SIZE_PRIORITY.indexOf(second); + + if (firstPriority > -1 || secondPriority > -1) { + if (firstPriority === -1) { + return 1; + } + if (secondPriority === -1) { + return -1; + } + return firstPriority - secondPriority; + } + + return first - second; + }); }; SmartyadsAdapter.createNew = function () { - return new SmartyadsAdapter(); + return new SmartyadsAdapter(); }; module.exports = SmartyadsAdapter; \ No newline at end of file From caf92b258912aba15ad3ea6aa1430556e9e0a7ba Mon Sep 17 00:00:00 2001 From: Matt Lane Date: Wed, 18 Jan 2017 16:25:03 -0800 Subject: [PATCH 18/27] Reset hb_* keys only for registered aduniits (#934) --- src/targeting.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/targeting.js b/src/targeting.js index 35c25224ce4..7299da9bbca 100644 --- a/src/targeting.js +++ b/src/targeting.js @@ -10,7 +10,13 @@ targeting.resetPresetTargeting = function() { if (isGptPubadsDefined()) { window.googletag.pubads().getSlots().forEach(slot => { pbTargetingKeys.forEach(function(key){ - slot.setTargeting(key,null); + // reset only registered adunits + $$PREBID_GLOBAL$$.adUnits.find(function(unit) { + if (unit.code === slot.getAdUnitPath() || + unit.code === slot.getSlotElementId()) { + slot.setTargeting(key, null); + } + }); }); }); } From 8303597028b08002398c18d9e85fe06a32bfe66e Mon Sep 17 00:00:00 2001 From: Nate Cozi Date: Wed, 18 Jan 2017 16:45:00 -0800 Subject: [PATCH 19/27] Maintenance/refactor hb deal (#935) * refactored the way hb_deal is handled in adserverTargeting * Add Sharethrough adapter (#865) * Rz/submit to prebid (#4) Added Sharethrough Adapter * fix warnings * added beacons * made compatible with chrome 37. other minor changes * win beacon fired in analytics adapter * specs for new analytics adapter * add try catch blocks. misc refactor * removed test page * remove debugger * refactor analytics adapter * removed test endpoint * analytics url parameter is empty * removed bidwon listener on adapter * removed analytics from package.json * refactor hb_deal targeting key as a standard key * rollback errant style fixes * more fixes --- src/adapters/sharethrough.js | 2 +- src/bidmanager.js | 14 +++++++------ src/constants.json | 3 ++- src/targeting.js | 26 ++++++------------------- test/spec/adapters/sharethrough_spec.js | 2 +- test/spec/bidmanager_spec.js | 5 ++++- 6 files changed, 22 insertions(+), 30 deletions(-) diff --git a/src/adapters/sharethrough.js b/src/adapters/sharethrough.js index 2732d714479..a9d26ecf23e 100644 --- a/src/adapters/sharethrough.js +++ b/src/adapters/sharethrough.js @@ -16,7 +16,7 @@ var SharethroughAdapter = function SharethroughAdapter() { const bids = params.bids; addEventListener("message", _receiveMessage, false); - + // cycle through bids for (let i = 0; i < bids.length; i += 1) { const bidRequest = bids[i]; diff --git a/src/bidmanager.js b/src/bidmanager.js index ffd4b24632b..380403b0d3d 100644 --- a/src/bidmanager.js +++ b/src/bidmanager.js @@ -130,12 +130,8 @@ exports.addBidResponse = function (adUnitCode, bid) { //if there is any key value pairs to map do here var keyValues = {}; - if (bid.bidderCode && (bid.cpm > 0 || bid.dealId)) { + if (bid.bidderCode && bid.cpm > 0) { keyValues = getKeyValueTargetingPairs(bid.bidderCode, bid); - - if (bid.dealId) { - keyValues[`hb_deal_${bid.bidderCode}`] = bid.dealId; - } } bid.adserverTargeting = keyValues; @@ -204,7 +200,8 @@ function setKeys(keyValues, bidderSettings, custBidObj) { } if ( - typeof bidderSettings.suppressEmptyKeys !== "undefined" && bidderSettings.suppressEmptyKeys === true && + (typeof bidderSettings.suppressEmptyKeys !== "undefined" && bidderSettings.suppressEmptyKeys === true || + key === "hb_deal") && // hb_deal is suppressed automatically if not set ( utils.isEmptyStr(value) || value === null || @@ -404,6 +401,11 @@ function getStandardBidderSettings() { val: function (bidResponse) { return bidResponse.size; } + }, { + key: 'hb_deal', + val: function (bidResponse) { + return bidResponse.dealId; + } } ] }; diff --git a/src/constants.json b/src/constants.json index 921670d476e..4d029c87458 100644 --- a/src/constants.json +++ b/src/constants.json @@ -54,6 +54,7 @@ "hb_bidder", "hb_adid", "hb_pb", - "hb_size" + "hb_size", + "hb_deal" ] } diff --git a/src/targeting.js b/src/targeting.js index 7299da9bbca..2abb8a0972d 100644 --- a/src/targeting.js +++ b/src/targeting.js @@ -29,8 +29,7 @@ targeting.getAllTargeting = function(adUnitCode) { // `alwaysUseBid=true`. If sending all bids is enabled, add targeting for losing bids. var targeting = getWinningBidTargeting(adUnitCodes) .concat(getAlwaysUseBidTargeting(adUnitCodes)) - .concat($$PREBID_GLOBAL$$._sendAllBids ? getBidLandscapeTargeting(adUnitCodes) : []) - .concat(getDealTargeting(adUnitCodes)); + .concat($$PREBID_GLOBAL$$._sendAllBids ? getBidLandscapeTargeting(adUnitCodes) : []); //store a reference of the targeting keys targeting.map(adUnitCode => { @@ -101,14 +100,8 @@ targeting.setTargetingForAst = function() { function getWinningBidTargeting() { let winners = targeting.getWinningBids(); - - // winning bids with deals need an hb_deal targeting key - // adding hb_deal to bid.adserverTargeting if it exists in winners array - winners - .filter(bid => bid.dealId) - .map(bid => bid.adserverTargeting.hb_deal = bid.dealId); - let standardKeys = getStandardKeys(); + winners = winners.map(winner => { return { [winner.adUnitCode]: Object.keys(winner.adserverTargeting) @@ -163,7 +156,10 @@ function getBidLandscapeTargeting(adUnitCodes) { .map(bid => { if (bid.adserverTargeting) { return { - [bid.adUnitCode]: getTargetingMap(bid, standardKeys) + [bid.adUnitCode]: getTargetingMap(bid, standardKeys.filter( + key => typeof bid.adserverTargeting[key] !== 'undefined') // mainly for possibly + // unset hb_deal + ) }; } }).filter(bid => bid); // removes empty elements in array @@ -177,16 +173,6 @@ function getTargetingMap(bid, keys) { }); } -function getDealTargeting() { - return $$PREBID_GLOBAL$$._bidsReceived.filter(bid => bid.dealId).map(bid => { - const dealKey = `hb_deal_${bid.bidderCode}`; - return { - [bid.adUnitCode]: getTargetingMap(bid, CONSTANTS.TARGETING_KEYS) - .concat({ [dealKey.substring(0, 20)]: [bid.adserverTargeting[dealKey]] }) - }; - }); -} - targeting.isApntagDefined = function() { if (window.apntag && utils.isFn(window.apntag.setKeywords)) { return true; diff --git a/test/spec/adapters/sharethrough_spec.js b/test/spec/adapters/sharethrough_spec.js index 83e207e0199..0678690370e 100644 --- a/test/spec/adapters/sharethrough_spec.js +++ b/test/spec/adapters/sharethrough_spec.js @@ -116,7 +116,7 @@ describe('sharethrough adapter', () => { ], "stxUserId": "" }; - + pbjs.strcallback(bidderReponse1); pbjs.strcallback(bidderReponse2); diff --git a/test/spec/bidmanager_spec.js b/test/spec/bidmanager_spec.js index cb2d60c7ca8..602f0beacfc 100644 --- a/test/spec/bidmanager_spec.js +++ b/test/spec/bidmanager_spec.js @@ -422,6 +422,9 @@ describe('bidmanager.js', function () { bidmanager.adjustBids(bid) assert.equal(bid.cpm, 0); + // reset bidderSettings so we don't mess up further tests + $$PREBID_GLOBAL$$.bidderSettings = {}; + }); }); @@ -474,7 +477,7 @@ describe('bidmanager.js', function () { bid.dealId = "test deal"; bidmanager.addBidResponse(bid.adUnitCode, bid); const addedBid = $$PREBID_GLOBAL$$._bidsReceived.pop(); - assert.equal(addedBid.adserverTargeting[`hb_deal_${bid.bidderCode}`], bid.dealId, 'dealId placed in adserverTargeting'); + assert.equal(addedBid.adserverTargeting[`hb_deal`], bid.dealId, 'dealId placed in adserverTargeting'); }); it('should not alter bid adID', () => { From 65d1d226b505df4d716e603309cf92311acaa064 Mon Sep 17 00:00:00 2001 From: itayo155 Date: Thu, 19 Jan 2017 06:53:27 +0200 Subject: [PATCH 20/27] Emit event when setTargetingForGPTAsync is called (#873) * emitting event when setTargetingForGPTAsync is called * emitting event when setTargetingForGPTAsync is called - cleanup * emitting event when setTargetingForGPTAsync is called - cleanup --- src/constants.json | 3 ++- src/prebid.js | 8 ++++++++ test/spec/unit/pbjs_api_spec.js | 12 ++++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/constants.json b/src/constants.json index 4d029c87458..aa88fae3178 100644 --- a/src/constants.json +++ b/src/constants.json @@ -34,7 +34,8 @@ "BID_TIMEOUT": "bidTimeout", "BID_REQUESTED": "bidRequested", "BID_RESPONSE": "bidResponse", - "BID_WON": "bidWon" + "BID_WON": "bidWon", + "SET_TARGETING": "setTargeting" }, "EVENT_ID_PATHS": { "bidWon": "adUnitCode" diff --git a/src/prebid.js b/src/prebid.js index def4e2ff738..49cdf9d06c1 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -25,6 +25,7 @@ var objectType_function = 'function'; var objectType_undefined = 'undefined'; var objectType_object = 'object'; var BID_WON = CONSTANTS.EVENTS.BID_WON; +var SET_TARGETING = CONSTANTS.EVENTS.SET_TARGETING; var auctionRunning = false; var bidRequestQueue = []; @@ -244,8 +245,12 @@ $$PREBID_GLOBAL$$.setTargetingForGPTAsync = function () { //first reset any old targeting targeting.resetPresetTargeting(); + //now set new targeting keys targeting.setTargeting(targeting.getAllTargeting()); + + //emit event + events.emit(SET_TARGETING); }; $$PREBID_GLOBAL$$.setTargetingForAst = function() { @@ -256,6 +261,9 @@ $$PREBID_GLOBAL$$.setTargetingForAst = function() { } targeting.setTargetingForAst(); + + //emit event + events.emit(SET_TARGETING); }; /** diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index 98538a5cd1a..c414ba1b8cb 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -483,6 +483,18 @@ describe('Unit: Prebid Module', function () { assert.ok(logErrorSpy.calledWith(error), 'expected error was logged'); window.googletag = windowGoogletagBackup; }); + + it('should emit SET_TARGETING event when successfully invoked', function() { + var slots = createSlotArray(); + window.googletag.pubads().setSlots(slots); + + var callback = sinon.spy(); + + $$PREBID_GLOBAL$$.onEvent('setTargeting', callback); + $$PREBID_GLOBAL$$.setTargetingForGPTAsync(config.adUnitCodes); + + sinon.assert.calledOnce(callback); + }) }); describe('allBidsAvailable', function () { From 9759715c824e95e373c374badfe5fdc3bb5f7e48 Mon Sep 17 00:00:00 2001 From: lntho Date: Thu, 19 Jan 2017 08:38:00 -0800 Subject: [PATCH 21/27] OpenX Adapter: Fixed bug regarding cross-domain iframe support (#931) --- src/adapters/openx.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/adapters/openx.js b/src/adapters/openx.js index 7033ee5482a..fe889cd9d78 100644 --- a/src/adapters/openx.js +++ b/src/adapters/openx.js @@ -68,8 +68,13 @@ const OpenxAdapter = function OpenxAdapter() { body; if (isIfr) { - tWin = window.top; - tDoc = window.top.document; + try { + tWin = window.top; + tDoc = window.top.document; + } + catch (e) { + return; + } docEl = tDoc.documentElement; body = tDoc.body; From 77ecc50958d4a9a326a9fc50450804c16fb36c71 Mon Sep 17 00:00:00 2001 From: Tiffany Wu Date: Thu, 19 Jan 2017 11:40:07 -0500 Subject: [PATCH 22/27] Add flash detection to TripleLift adapter (#855) * add flash detection to TripleLift adapter * add window --- src/adapters/triplelift.js | 47 +++++++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/src/adapters/triplelift.js b/src/adapters/triplelift.js index ae69a4b6149..b629e414dec 100644 --- a/src/adapters/triplelift.js +++ b/src/adapters/triplelift.js @@ -14,17 +14,16 @@ var TripleLiftAdapter = function TripleLiftAdapter() { var tlReq = params.bids; var bidsCount = tlReq.length; - //set expected bids count for callback execution - //bidmanager.setExpectedBidsCount('triplelift',bidsCount); + // set expected bids count for callback execution + // bidmanager.setExpectedBidsCount('triplelift',bidsCount); for (var i = 0; i < bidsCount; i++) { var bidRequest = tlReq[i]; var callbackId = bidRequest.bidId; adloader.loadScript(buildTLCall(bidRequest, callbackId)); - //store a reference to the bidRequest from the callback id - //bidmanager.pbCallbackMap[callbackId] = bidRequest; + // store a reference to the bidRequest from the callback id + // bidmanager.pbCallbackMap[callbackId] = bidRequest; } - } @@ -33,8 +32,7 @@ var TripleLiftAdapter = function TripleLiftAdapter() { var inventoryCode = utils.getBidIdParameter('inventoryCode', bid.params); var floor = utils.getBidIdParameter('floor', bid.params); - - //build our base tag, based on if we are http or https + // build our base tag, based on if we are http or https var tlURI = '//tlx.3lift.com/header/auction?'; var tlCall = document.location.protocol + tlURI; @@ -45,17 +43,20 @@ var TripleLiftAdapter = function TripleLiftAdapter() { tlCall = utils.tryAppendQueryString(tlCall, 'inv_code', inventoryCode); tlCall = utils.tryAppendQueryString(tlCall, 'floor', floor); - //sizes takes a bit more logic + // indicate whether flash support exists + tlCall = utils.tryAppendQueryString(tlCall, 'fe', isFlashEnabled()); + + // sizes takes a bit more logic var sizeQueryString = utils.parseSizesInput(bid.sizes); if (sizeQueryString) { tlCall += 'size=' + sizeQueryString + '&'; } - //append referrer + // append referrer var referrer = utils.getTopWindowUrl(); tlCall = utils.tryAppendQueryString(tlCall, 'referrer', referrer); - //remove the trailing "&" + // remove the trailing "&" if (tlCall.lastIndexOf('&') === tlCall.length - 1) { tlCall = tlCall.substring(0, tlCall.length - 1); } @@ -64,22 +65,36 @@ var TripleLiftAdapter = function TripleLiftAdapter() { utils.logMessage('tlCall request built: ' + tlCall); // @endif - //append a timer here to track latency + // append a timer here to track latency bid.startTime = new Date().getTime(); return tlCall; - } + function isFlashEnabled() { + var hasFlash = 0; + try { + // check for Flash support in IE + var fo = new window.ActiveXObject('ShockwaveFlash.ShockwaveFlash'); + if (fo) { hasFlash = 1; } + } catch (e) { + if (navigator.mimeTypes && + navigator.mimeTypes['application/x-shockwave-flash'] !== undefined && + navigator.mimeTypes['application/x-shockwave-flash'].enabledPlugin) { + hasFlash = 1; + } + } + return hasFlash; + } - //expose the callback to the global object: + // expose the callback to the global object: $$PREBID_GLOBAL$$.TLCB = function(tlResponseObj) { if (tlResponseObj && tlResponseObj.callback_id) { var bidObj = utils.getBidRequest(tlResponseObj.callback_id); var placementCode = bidObj && bidObj.placementCode; // @if NODE_ENV='debug' - if (bidObj) {utils.logMessage('JSONP callback function called for inventory code: ' + bidObj.params.inventoryCode);} + if (bidObj) { utils.logMessage('JSONP callback function called for inventory code: ' + bidObj.params.inventoryCode); } // @endif var bid = []; @@ -95,7 +110,7 @@ var TripleLiftAdapter = function TripleLiftAdapter() { bidmanager.addBidResponse(placementCode, bid); } else { - //no response data + // no response data // @if NODE_ENV='debug' if (bidObj) {utils.logMessage('No prebid response from TripleLift for inventory code: ' + bidObj.params.inventoryCode);} // @endif @@ -105,7 +120,7 @@ var TripleLiftAdapter = function TripleLiftAdapter() { } } else { - //no response data + // no response data // @if NODE_ENV='debug' utils.logMessage('No prebid response for placement %%PLACEMENT%%'); // @endif From 5b89db8e529769953b74ae8224010d9627a78ef0 Mon Sep 17 00:00:00 2001 From: Matt Kendall Date: Thu, 19 Jan 2017 14:10:20 -0500 Subject: [PATCH 23/27] Xaxis adapter submitted by Daniel hoffmann (#938) * xhb adapter added - use AppNexus test ad unit * adjusted adapter to set responseCPM to 0 and add in dealId * implemented suggested changes * implemented suggested changes * ran jscs fixer for xhb.js and added it to adapters.json * Fixed adapter code and style --- adapters.json | 1 + src/adapters/xhb.js | 147 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 148 insertions(+) create mode 100644 src/adapters/xhb.js diff --git a/adapters.json b/adapters.json index e373420be71..80aad3a9cf0 100644 --- a/adapters.json +++ b/adapters.json @@ -39,6 +39,7 @@ "underdogmedia", "memeglobal", "centro", + "xhb", "sharethrough", "roxot", "vertoz", diff --git a/src/adapters/xhb.js b/src/adapters/xhb.js new file mode 100644 index 00000000000..722d97ebc91 --- /dev/null +++ b/src/adapters/xhb.js @@ -0,0 +1,147 @@ +import {getBidRequest} from '../utils.js'; + +const CONSTANTS = require('../constants.json'); +const utils = require('../utils.js'); +const adloader = require('../adloader.js'); +const bidmanager = require('../bidmanager.js'); +const bidfactory = require('../bidfactory.js'); + +const XhbAdapter = function XhbAdapter() { + + function buildJPTCall(bid, callbackId) { + //determine tag params + const placementId = utils.getBidIdParameter('placementId', bid.params); + const inventoryCode = utils.getBidIdParameter('invCode', bid.params); + let referrer = utils.getBidIdParameter('referrer', bid.params); + const altReferrer = utils.getBidIdParameter('alt_referrer', bid.params); + + //Always use https + let jptCall = 'https://ib.adnxs.com/jpt?'; + + jptCall = utils.tryAppendQueryString(jptCall, 'callback', '$$PREBID_GLOBAL$$.handleXhbCB'); + jptCall = utils.tryAppendQueryString(jptCall, 'callback_uid', callbackId); + jptCall = utils.tryAppendQueryString(jptCall, 'id', placementId); + jptCall = utils.tryAppendQueryString(jptCall, 'code', inventoryCode); + + //sizes takes a bit more logic + let sizeQueryString = ''; + let parsedSizes = utils.parseSizesInput(bid.sizes); + + //combine string into proper querystring for impbus + let parsedSizesLength = parsedSizes.length; + if (parsedSizesLength > 0) { + //first value should be "size" + sizeQueryString = 'size=' + parsedSizes[0]; + if (parsedSizesLength > 1) { + //any subsequent values should be "promo_sizes" + sizeQueryString += '&promo_sizes='; + for (let j = 1; j < parsedSizesLength; j++) { + sizeQueryString += parsedSizes[j] += ','; + } + //remove trailing comma + if (sizeQueryString && sizeQueryString.charAt(sizeQueryString.length - 1) === ',') { + sizeQueryString = sizeQueryString.slice(0, sizeQueryString.length - 1); + } + } + } + + if (sizeQueryString) { + jptCall += sizeQueryString + '&'; + } + + //append custom attributes: + let paramsCopy = utils.extend({}, bid.params); + + //delete attributes already used + delete paramsCopy.placementId; + delete paramsCopy.invCode; + delete paramsCopy.query; + delete paramsCopy.referrer; + delete paramsCopy.alt_referrer; + + //get the reminder + let queryParams = utils.parseQueryStringParameters(paramsCopy); + + //append + if (queryParams) { + jptCall += queryParams; + } + + //append referrer + if (referrer === '') { + referrer = utils.getTopWindowUrl(); + } + + jptCall = utils.tryAppendQueryString(jptCall, 'referrer', referrer); + jptCall = utils.tryAppendQueryString(jptCall, 'alt_referrer', altReferrer); + + //remove the trailing "&" + if (jptCall.lastIndexOf('&') === jptCall.length - 1) { + jptCall = jptCall.substring(0, jptCall.length - 1); + } + + return jptCall; + } + + //expose the callback to the global object: + $$PREBID_GLOBAL$$.handleXhbCB = function (jptResponseObj) { + let bidCode; + + if (jptResponseObj && jptResponseObj.callback_uid) { + + let responseCPM; + let id = jptResponseObj.callback_uid; + let placementCode = ''; + let bidObj = getBidRequest(id); + if (bidObj) { + bidCode = bidObj.bidder; + placementCode = bidObj.placementCode; + //set the status + bidObj.status = CONSTANTS.STATUS.GOOD; + } + + let bid = []; + if (jptResponseObj.result && jptResponseObj.result.ad && jptResponseObj.result.ad !== '') { + responseCPM = 0.00; + + //store bid response + //bid status is good (indicating 1) + let adId = jptResponseObj.result.creative_id; + bid = bidfactory.createBid(CONSTANTS.STATUS.GOOD, bidObj); + bid.creative_id = adId; + bid.bidderCode = bidCode; + bid.cpm = responseCPM; + bid.adUrl = jptResponseObj.result.ad; + bid.width = jptResponseObj.result.width; + bid.height = jptResponseObj.result.height; + bid.dealId = '99999999'; + + bidmanager.addBidResponse(placementCode, bid); + + } else { + //no response data + //indicate that there is no bid for this placement + bid = bidfactory.createBid(2); + bid.bidderCode = bidCode; + bidmanager.addBidResponse(placementCode, bid); + } + } + }; + + function _callBids(params) { + let bids = params.bids || []; + for (let i = 0; i < bids.length; i++) { + let bid = bids[i]; + let callbackId = bid.bidId; + adloader.loadScript(buildJPTCall(bid, callbackId)); + } + } + + // Export the callBids function, so that prebid.js can execute + // this function when the page asks to send out bid requests. + return { + callBids: _callBids + }; +}; + +module.exports = XhbAdapter; From 4aaa75c78b3b6504fd7692c45b7847909ec4ca3f Mon Sep 17 00:00:00 2001 From: Nate Cozi Date: Thu, 19 Jan 2017 12:20:19 -0800 Subject: [PATCH 24/27] add an event that fires when requestBids is called (#939) --- src/constants.json | 6 ++++-- src/prebid.js | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/constants.json b/src/constants.json index aa88fae3178..47ce9af55e5 100644 --- a/src/constants.json +++ b/src/constants.json @@ -19,7 +19,8 @@ "TYPE": { "ALL_BIDS_BACK": "allRequestedBidsBack", "AD_UNIT_BIDS_BACK": "adUnitBidsBack", - "BID_WON": "bidWon" + "BID_WON": "bidWon", + "REQUEST_BIDS": "requestBids" } }, "objectType_function": "function", @@ -35,7 +36,8 @@ "BID_REQUESTED": "bidRequested", "BID_RESPONSE": "bidResponse", "BID_WON": "bidWon", - "SET_TARGETING": "setTargeting" + "SET_TARGETING": "setTargeting", + "REQUEST_BIDS": "requestBids" }, "EVENT_ID_PATHS": { "bidWon": "adUnitCode" diff --git a/src/prebid.js b/src/prebid.js index 49cdf9d06c1..d22dd462373 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -359,6 +359,7 @@ $$PREBID_GLOBAL$$.clearAuction = function() { * @param adUnitCodes */ $$PREBID_GLOBAL$$.requestBids = function ({ bidsBackHandler, timeout, adUnits, adUnitCodes }) { + events.emit('requestBids'); const cbTimeout = $$PREBID_GLOBAL$$.cbTimeout = timeout || $$PREBID_GLOBAL$$.bidderTimeout; adUnits = adUnits || $$PREBID_GLOBAL$$.adUnits; From b88ecb91d5d0558ac561019ebad92c9faae00dc7 Mon Sep 17 00:00:00 2001 From: icaroulle Date: Thu, 19 Jan 2017 21:23:12 +0100 Subject: [PATCH 25/27] Add Criteo adapter (#928) * Add Criteo adapter * Removing integration example * Fix for the hint task --- .jshintrc | 3 +- adapters.json | 1 + src/adapters/criteo.js | 142 +++++++++++++++++++++++++ test/spec/adapters/criteo_spec.js | 171 ++++++++++++++++++++++++++++++ 4 files changed, 316 insertions(+), 1 deletion(-) create mode 100644 src/adapters/criteo.js create mode 100644 test/spec/adapters/criteo_spec.js diff --git a/.jshintrc b/.jshintrc index a2331cc9b8b..a51c32f4328 100644 --- a/.jshintrc +++ b/.jshintrc @@ -33,6 +33,7 @@ "sinon": false, "beforeEach": false, "afterEach": false, - "JSON": true + "JSON": true, + "Criteo": true } } diff --git a/adapters.json b/adapters.json index 80aad3a9cf0..f1aed77788a 100644 --- a/adapters.json +++ b/adapters.json @@ -38,6 +38,7 @@ "jcm", "underdogmedia", "memeglobal", + "criteo", "centro", "xhb", "sharethrough", diff --git a/src/adapters/criteo.js b/src/adapters/criteo.js new file mode 100644 index 00000000000..25759c30876 --- /dev/null +++ b/src/adapters/criteo.js @@ -0,0 +1,142 @@ +var bidfactory = require('../bidfactory.js'); +var bidmanager = require('../bidmanager.js'); +var adloader = require('../adloader'); + +var CriteoAdapter = function CriteoAdapter() { + + var _publisherTagUrl = window.location.protocol + '//static.criteo.net/js/ld/publishertag.js'; + var _bidderCode = 'criteo'; + var _profileId = 125; + + function _callBids(params) { + if (!window.criteo_pubtag || window.criteo_pubtag instanceof Array) { + // publisherTag not loaded yet + + _pushBidRequestEvent(params); + adloader.loadScript( + _publisherTagUrl, + function () {}, + true + ); + } + else { + // publisherTag already loaded + _pushBidRequestEvent(params); + } + } + + // send bid request to criteo direct bidder handler + function _pushBidRequestEvent(params) { + + // if we want to be fully asynchronous, we must first check window.criteo_pubtag in case publishertag.js is not loaded yet. + window.Criteo = window.Criteo || {}; + window.Criteo.events = window.Criteo.events || []; + + // generate the bidding event + var biddingEventFunc = function () { + + var bids = params.bids || []; + + var slots = []; + + var isAudit = false; + + // build slots before sending one multi-slots bid request + for (var i = 0; i < bids.length; i++) { + var bid = bids[i]; + slots.push( + new Criteo.PubTag.DirectBidding.DirectBiddingSlot( + bid.placementCode, + bid.params.zoneId + ) + ); + + isAudit |= bid.params.audit !== undefined; + } + + var biddingEvent = new Criteo.PubTag.DirectBidding.DirectBiddingEvent( + _profileId, + new Criteo.PubTag.DirectBidding.DirectBiddingUrlBuilder(isAudit), + slots, + _callbackSuccess(slots), + _callbackError(slots), + _callbackError(slots) // timeout handled as error + ); + + // process the event as soon as possible + window.criteo_pubtag.push(biddingEvent); + }; + + window.Criteo.events.push(biddingEventFunc); + + } + + function parseBidResponse(bidsResponse) { + try { + return JSON.parse(bidsResponse); + } + catch (error) { + return {}; + } + } + + function isNoBidResponse(jsonbidsResponse) { + return jsonbidsResponse.slots === undefined; + } + + function _callbackSuccess(slots) { + return function (bidsResponse) { + var jsonbidsResponse = parseBidResponse(bidsResponse); + + if (isNoBidResponse(jsonbidsResponse)) + return _callbackError(slots)(); + + for (var i = 0; i < slots.length; i++) { + var bidResponse = null; + + // look for the matching bid response + for (var j = 0; j < jsonbidsResponse.slots.length; j++) { + if (jsonbidsResponse.slots[j] && jsonbidsResponse.slots[j].impid === slots[i].impId) { + bidResponse = jsonbidsResponse.slots.splice(j, 1)[0]; + break; + } + } + + // register the bid response + var bidObject; + if (bidResponse) { + bidObject = bidfactory.createBid(1); + bidObject.bidderCode = _bidderCode; + bidObject.cpm = bidResponse.cpm; + bidObject.ad = bidResponse.creative; + bidObject.width = bidResponse.width; + bidObject.height = bidResponse.height; + } + else { + bidObject = _invalidBidResponse(); + } + bidmanager.addBidResponse(slots[i].impId, bidObject); + } + }; + } + + function _callbackError(slots) { + return function () { + for (var i = 0; i < slots.length; i++) { + bidmanager.addBidResponse(slots[i].impId, _invalidBidResponse()); + } + }; + } + + function _invalidBidResponse() { + var bidObject = bidfactory.createBid(2); + bidObject.bidderCode = _bidderCode; + return bidObject; + } + + return { + callBids: _callBids + }; +}; + +module.exports = CriteoAdapter; \ No newline at end of file diff --git a/test/spec/adapters/criteo_spec.js b/test/spec/adapters/criteo_spec.js new file mode 100644 index 00000000000..5b2159e1594 --- /dev/null +++ b/test/spec/adapters/criteo_spec.js @@ -0,0 +1,171 @@ +/* jshint -W030 */ + +import Adapter from '../../../src/adapters/criteo'; +import bidManager from '../../../src/bidmanager'; +import {expect} from 'chai'; + +var CONSTANTS = require('../../../src/constants'); + +describe('criteo adapter test', () => { + + let adapter; + let stubAddBidResponse; + + let validBid = { + bidderCode: 'criteo', + bids: [ + { + bidder: 'criteo', + placementCode: 'foo', + sizes: [[250, 350]], + params: { + zoneId: 32934, + audit: 'true' + } + } + ] + }; + + let validResponse = {slots: [{impid: "foo", cpm: 1.12, creative: ""}]}; + let invalidResponse = { slots: [{ "impid": "unknownSlot" }] } + + let validMultiBid = { + bidderCode: 'criteo', + bids: [ + { + bidder: 'criteo', + placementCode: 'foo', + sizes: [[250, 350]], + params: { + zoneId: 32934, + audit: 'true' + } + }, + { + bidder: 'criteo', + placementCode: 'bar', + sizes: [[250, 350]], + params: { + zoneId: 32935, + audit: 'true' + } + } + ] + }; + + beforeEach(() => { + adapter = new Adapter(); + }); + + afterEach(() => { + stubAddBidResponse.restore(); + }); + +describe('adding bids to the manager', () => { + let server; + + beforeEach(() => { + server = sinon.fakeServer.create({autoRespond: true, respondImmediately: true}); + server.respondWith(JSON.stringify(validResponse)); + }); + + it('adds bid for valid request', (done) => { + stubAddBidResponse = sinon.stub(bidManager, 'addBidResponse', function (adUnitCode, bid) { + expect(bid).to.satisfy(bid => { return bid.getStatusCode() == CONSTANTS.STATUS.GOOD }); + done(); + }); + + adapter.callBids(validBid); + }); + + it('adds bid for multibid valid request', (done) => { + let callCount = 0; + stubAddBidResponse = sinon.stub(bidManager, 'addBidResponse', function (adUnitCode, bid) { + callCount++; + + if (callCount == 2) + done(); + }); + + adapter.callBids(validMultiBid); + }); + + it('adds bidderCode to the response of a valid request', (done) => { + stubAddBidResponse = sinon.stub(bidManager, 'addBidResponse', function (adUnitCode, bid) { + expect(bid).to.have.property('bidderCode', 'criteo'); + done(); + }); + + adapter.callBids(validBid); + }); + + it('adds cpm to the response of a valid request', (done) => { + stubAddBidResponse = sinon.stub(bidManager, 'addBidResponse', function (adUnitCode, bid) { + expect(bid).to.have.property('cpm', 1.12); + done(); + }); + adapter.callBids(validBid); + }); + + it('adds creative to the response of a valid request', (done) => { + stubAddBidResponse = sinon.stub(bidManager, 'addBidResponse', function (adUnitCode, bid) { + expect(bid).to.have.property('ad', ""); + done(); + }); + adapter.callBids(validBid); + }); +}); + +describe('dealing with unexpected situations', () => { + let server; + + beforeEach(() => { + server = sinon.fakeServer.create({autoRespond: true, respondImmediately: true}); + }); + + it('no bid if cdb handler responds with no bid empty string response', (done) => { + server.respondWith(""); + + stubAddBidResponse = sinon.stub(bidManager, 'addBidResponse', function (adUnitCode, bid) { + expect(bid).to.satisfy(bid => { return bid.getStatusCode() == CONSTANTS.STATUS.NO_BID }); + done(); + }); + + adapter.callBids(validBid); + }); + + it('no bid if cdb handler responds with no bid empty object response', (done) => { + server.respondWith("{ }"); + + stubAddBidResponse = sinon.stub(bidManager, 'addBidResponse', function (adUnitCode, bid) { + expect(bid).to.satisfy(bid => { return bid.getStatusCode() == CONSTANTS.STATUS.NO_BID }); + done(); + }); + + adapter.callBids(validBid); + }); + + it('no bid if cdb handler responds with HTTP error', (done) => { + server.respondWith([500, {}, "Internal Server Error"]); + + stubAddBidResponse = sinon.stub(bidManager, 'addBidResponse', function (adUnitCode, bid) { + expect(bid).to.satisfy(bid => { return bid.getStatusCode() == CONSTANTS.STATUS.NO_BID }); + done(); + }); + + adapter.callBids(validBid); + }); + + it('no bid if response is invalid because response slots don\'t match input slots', (done) => { + server.respondWith(JSON.stringify(invalidResponse)); + + stubAddBidResponse = sinon.stub(bidManager, 'addBidResponse', function (adUnitCode, bid) { + expect(bid).to.satisfy(bid => { return bid.getStatusCode() == CONSTANTS.STATUS.NO_BID }); + done(); + }); + + adapter.callBids(validBid); + }); + }); + +}); From 12a6303321a02779a428fc81db95cbc8b2ebc030 Mon Sep 17 00:00:00 2001 From: Matt Lane Date: Mon, 23 Jan 2017 14:04:49 -0500 Subject: [PATCH 26/27] Prebid 0.18.0 Release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d9bdd0f7f7f..ec6ea57e99c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "0.17.0", + "version": "0.18.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From e145489bc5dd6d292cf16e7fe80adfdf991562ac Mon Sep 17 00:00:00 2001 From: Zhukovsky Date: Mon, 6 Mar 2017 14:45:08 +0300 Subject: [PATCH 27/27] Add changelog entry. --- CHANGELOG | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 40eb2a027df..50fa11f6789 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,7 +1,13 @@ +AOL Prebid 1.13.0 +---------------- +Updated to Prebid 0.18.0 + + AOL Prebid 1.12.0 ---------------- Updated to Prebid 0.17.0 + AOL Prebid 1.11.0 ---------------- Added functionality of cookie syncing upon bid response.