From e1c50106876d3e12990fc74ac00665ad54e4897c Mon Sep 17 00:00:00 2001 From: Bart van Bragt Date: Wed, 23 Dec 2015 14:38:22 +0100 Subject: [PATCH 001/160] Don't return empty zones Criteo returns something like: zone1;zone2;zone3; in their cookie. This results in an array like: ['zone1', 'zone2', 'zone3', ''] We don't want this empty element. To prevent this from happening we remove the trailing semicolon from the cookie string before split(). --- src/adapters/criteo.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/adapters/criteo.js b/src/adapters/criteo.js index 75d5e928e2c..5da825396f7 100644 --- a/src/adapters/criteo.js +++ b/src/adapters/criteo.js @@ -53,7 +53,7 @@ var CriteoAdapter = function CriteoAdapter() { adResponse = bidfactory.createBid(1); adResponse.bidderCode = 'criteo'; - adResponse.keys = content.split(';'); + adResponse.keys = content.replace(/\;$/, '').split(';'); } else { // Indicate an ad was not returned adResponse = bidfactory.createBid(2); @@ -71,4 +71,4 @@ var CriteoAdapter = function CriteoAdapter() { }; }; -module.exports = CriteoAdapter; \ No newline at end of file +module.exports = CriteoAdapter; From f7541bd44e8d4c0422f9949cdea575f8b2965160 Mon Sep 17 00:00:00 2001 From: hjeong12 Date: Thu, 7 Jan 2016 15:42:46 +0900 Subject: [PATCH 002/160] update unit tests --- gulpfile.js | 15 ++++++++-- src/prebid.js | 32 --------------------- test/spec/adUnits_spec.js | 9 ++++-- test/spec/aliasBidder_spec.js | 7 +++-- test/spec/api_spec.js | 6 +++- test/spec/bids_spec.js | 10 +++++-- test/spec/utils_spec.js | 54 ++++++++++++++++++----------------- 7 files changed, 62 insertions(+), 71 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index 141cac6d585..f18f08e4114 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -154,8 +154,8 @@ gulp.task('quality', ['jscs'], function(cb){ }); gulp.task('watch', function () { - gulp.watch(['src/**/*.js'], ['build-dev']); - //gulp.watch(["test/tests.js", "src/*"], ["browserify", "unit-tests"]); + // gulp.watch(['src/**/*.js'], ['build-dev']); + gulp.watch(["test/tests.js", "src/*","test/spec/*.js"], ["browserify", "unit-tests"]); }); @@ -195,7 +195,16 @@ gulp.task("browser-sync", function () { //see http://fettblog.eu/gulp-browserify-multiple-bundles/ gulp.task("browserify", function() { "use strict"; - return browserify("./test/test.js") + var files =[ + "./test/test.js", + "./test/spec/api_spec.js", + "./test/spec/utils_spec.js", + "./test/spec/bids_spec.js", + "./test/spec/adUnits_spec.js", + "./test/spec/aliasBidder_spec.js" + ]; + + return browserify({ entries: [files] }) .bundle() .on("error", function (err) { console.log(err.toString()); diff --git a/src/prebid.js b/src/prebid.js index ec43cda09d6..5ff398016dd 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -896,36 +896,4 @@ pbjs_testonly.getAdUnits = function() { pbjs_testonly.clearAllAdUnits = function(){ pbjs.adUnits =[]; }; - -pbjs_testonly.utils_replaceTokenInString = function(str, map, token){ - return utils.replaceTokenInString(str, map, token); -}; - -pbjs_testonly.utils_getBidIdParamater = function(key, paramsObj) { - return utils.getBidIdParamater(key, paramsObj); -}; - -pbjs_testonly.utils_tryAppendQueryString = function(existingUrl, key, value){ - return utils.tryAppendQueryString(existingUrl, key, value); -}; - -pbjs_testonly.utils_parseQueryStringParameters = function(obj){ - return utils.parseQueryStringParameters(obj); -}; - -pbjs_testonly.utils_transformAdServerTargetingObj = function(obj){ - return utils.transformAdServerTargetingObj(obj); -}; - -pbjs_testonly.utils_extend = function(target, source){ - return utils.extend(target, source); -}; - -pbjs_testonly.utils_parseSizesInput = function(obj){ - return utils.parseSizesInput(obj); -}; - -pbjs_testonly.utils_parseGPTSingleSizeArray = function(singleSize){ - return utils.parseGPTSingleSizeArray(singleSize); -}; // @endif diff --git a/test/spec/adUnits_spec.js b/test/spec/adUnits_spec.js index 5d2ffa4f1b2..9fbc29b6860 100644 --- a/test/spec/adUnits_spec.js +++ b/test/spec/adUnits_spec.js @@ -1,7 +1,10 @@ describe("Publisher API _ AdUnits", function() { - var assert = chai.assert, - should = chai.should(), - expect = chai.expect; + var assert = require('chai').assert, + expect = require('chai').expect, + should = require('chai').should(); + + var prebid = require('../../src/prebid'); + before(function(){ var adUnits = [{ diff --git a/test/spec/aliasBidder_spec.js b/test/spec/aliasBidder_spec.js index 21809e36c72..c2410399bfc 100644 --- a/test/spec/aliasBidder_spec.js +++ b/test/spec/aliasBidder_spec.js @@ -1,7 +1,8 @@ describe("Publisher API _ Alias Bidder", function() { - var assert = chai.assert, - should = chai.should(), - expect = chai.expect; + var assert = require('chai').assert, + expect = require('chai').expect, + should = require('chai').should(); + var prebid = require('../../src/prebid'); before(function(){ diff --git a/test/spec/api_spec.js b/test/spec/api_spec.js index 98de887233a..fd6f296e746 100755 --- a/test/spec/api_spec.js +++ b/test/spec/api_spec.js @@ -1,5 +1,9 @@ +var assert = require('chai').assert; +var prebid = require('../../src/prebid'); + + describe("Publisher API", function() { - var assert = chai.assert; + // var assert = chai.assert; describe('api of command queue',function(){ diff --git a/test/spec/bids_spec.js b/test/spec/bids_spec.js index 0d885226f48..7b64fbaedee 100644 --- a/test/spec/bids_spec.js +++ b/test/spec/bids_spec.js @@ -1,7 +1,11 @@ + describe("Publisher API _ Bids", function() { - var assert = chai.assert, - expect = chai.expect, - should = chai.should(); + var assert = require('chai').assert, + expect = require('chai').expect, + should = require('chai').should(); + + var prebid = require('../../src/prebid'); + var rightSlotCode = '/19968336/header-bid-tag-0'; var rightDivCode = 'div-gpt-ad-1438287399331-0'; diff --git a/test/spec/utils_spec.js b/test/spec/utils_spec.js index bb27c221155..ecd7f94fff6 100755 --- a/test/spec/utils_spec.js +++ b/test/spec/utils_spec.js @@ -1,5 +1,7 @@ +var assert = require("assert"); +var utils = require('../../src/utils'); + describe("Utils", function() { - var assert = chai.assert; describe('replaceTokenInString', function(){ @@ -9,12 +11,12 @@ describe("Utils", function() { 'zap': 'quux' }; - var output = pbjs_testonly.utils_replaceTokenInString("hello %FOO%, I am %ZAP%", tokensToReplace, "%"); + var output = utils.replaceTokenInString("hello %FOO%, I am %ZAP%", tokensToReplace, "%"); assert.equal(output, "hello bar, I am quux"); }); it('should ignore tokens it does not see', function() { - var output = pbjs_testonly.utils_replaceTokenInString("hello %FOO%", {}, "%"); + var output = utils.replaceTokenInString("hello %FOO%", {}, "%"); assert.equal(output, "hello %FOO%"); }); @@ -26,7 +28,7 @@ describe("Utils", function() { 'a' : 'valueA', 'b' : 'valueB' } - var output = pbjs_testonly.utils_getBidIdParamater('a',obj); + var output = utils.getBidIdParamater('a',obj); assert.equal(output,'valueA'); }); @@ -35,7 +37,7 @@ describe("Utils", function() { 'a' : 'valueA', 'b' : 'valueB' } - var output = pbjs_testonly.utils_getBidIdParamater('c',obj); + var output = utils.getBidIdParamater('c',obj); assert.equal(output,''); }); }); @@ -46,7 +48,7 @@ describe("Utils", function() { var key = 'b'; var value = 'c'; - var output = pbjs_testonly.utils_tryAppendQueryString(url, key, value); + var output = utils.tryAppendQueryString(url, key, value); var expectedResult = url + key + "=" + encodeURIComponent(value) + '&'; assert.equal(output,expectedResult); @@ -57,7 +59,7 @@ describe("Utils", function() { var key = 'b'; var value = ''; - var output = pbjs_testonly.utils_tryAppendQueryString(url, key, value); + var output = utils.tryAppendQueryString(url, key, value); assert.equal(output,url); }); }); @@ -69,14 +71,14 @@ describe("Utils", function() { 'b':'2' }; - var output = pbjs_testonly.utils_parseQueryStringParameters(obj); + var output = utils.parseQueryStringParameters(obj); var expectedResult = "a=" + encodeURIComponent('1') + "&b=" + encodeURIComponent('2') + "&"; assert.equal(output,expectedResult); }); it('should return an empty string, if input obj is empty',function(){ var obj ={}; - var output = pbjs_testonly.utils_parseQueryStringParameters(obj); + var output = utils.parseQueryStringParameters(obj); assert.equal(output,''); }); }); @@ -88,14 +90,14 @@ describe("Utils", function() { 'b':'2' }; - var output = pbjs_testonly.utils_transformAdServerTargetingObj(obj); + var output = utils.transformAdServerTargetingObj(obj); var expectedResult = "a=" + encodeURIComponent('1') + "&b=" + encodeURIComponent('2') + "&"; assert.equal(output,expectedResult); }); it('should return an empty string, if input obj is empty',function(){ var obj ={}; - var output = pbjs_testonly.utils_transformAdServerTargetingObj(obj); + var output = utils.transformAdServerTargetingObj(obj); assert.equal(output,''); }); }); @@ -117,7 +119,7 @@ describe("Utils", function() { 'c':'3' }; - var output = pbjs_testonly.utils_extend(target, source); + var output = utils.extend(target, source); assert.deepEqual(output,expectedResult); }); @@ -127,7 +129,7 @@ describe("Utils", function() { 'c':'3' }; - var output = pbjs_testonly.utils_extend(target, source); + var output = utils.extend(target, source); assert.deepEqual(output,source); }); @@ -138,7 +140,7 @@ describe("Utils", function() { }; var source = {}; - var output = pbjs_testonly.utils_extend(target, source); + var output = utils.extend(target, source); assert.deepEqual(output,target); }); }); @@ -147,25 +149,25 @@ describe("Utils", function() { it('should return query string using multi size array',function(){ var sizes = [[728, 90], [970, 90]]; - var output = pbjs_testonly.utils_parseSizesInput(sizes); + var output = utils.parseSizesInput(sizes); assert.equal(output,'size=728x90&promo_sizes=970x90'); }); it('should return query string using single size array',function(){ var sizes = [728, 90]; - var output = pbjs_testonly.utils_parseSizesInput(sizes); + var output = utils.parseSizesInput(sizes); assert.equal(output,'size=728x90'); }); it('should return query string using string input',function(){ var sizes = '300x250,970x90'; - var output = pbjs_testonly.utils_parseSizesInput(sizes); + var output = utils.parseSizesInput(sizes); assert.equal(output,'size=300x250&promo_sizes=970x90'); }); it('return undefined if input array is empty',function(){ var sizes =[]; - var output = pbjs_testonly.utils_parseSizesInput(sizes); + var output = utils.parseSizesInput(sizes); assert.equal(output,undefined); }); }); @@ -174,49 +176,49 @@ describe("Utils", function() { it('should return size string with input single size array',function(){ var size = [300,250]; - var output = pbjs_testonly.utils_parseGPTSingleSizeArray(size); + var output = utils.parseGPTSingleSizeArray(size); assert.equal(output,'300x250'); }); it('should return size string with input single size array',function(){ var size =['300','250']; - var output = pbjs_testonly.utils_parseGPTSingleSizeArray(size); + var output = utils.parseGPTSingleSizeArray(size); assert.equal(output,'300x250'); }); it('return undefined using string input',function(){ var size ='1'; - var output = pbjs_testonly.utils_parseGPTSingleSizeArray(size); + var output = utils.parseGPTSingleSizeArray(size); assert.equal(output,undefined); }); it('return undefined using number input',function(){ var size =1; - var output = pbjs_testonly.utils_parseGPTSingleSizeArray(size); + var output = utils.parseGPTSingleSizeArray(size); assert.equal(output,undefined); }); it('return undefined using one length single array',function(){ var size =[300]; - var output = pbjs_testonly.utils_parseGPTSingleSizeArray(size); + var output = utils.parseGPTSingleSizeArray(size); assert.equal(output,undefined); }); it('return undefined if the input is empty',function(){ var size =''; - var output = pbjs_testonly.utils_parseGPTSingleSizeArray(size); + var output = utils.parseGPTSingleSizeArray(size); assert.equal(output,undefined); }); it('return undefined if the input is not a number',function(){ var size =['foo','bar']; - var output = pbjs_testonly.utils_parseGPTSingleSizeArray(size); + var output = utils.parseGPTSingleSizeArray(size); assert.equal(output,undefined); }); it('return undefined if the input is not a number 2',function(){ var size =['foo',300]; - var output = pbjs_testonly.utils_parseGPTSingleSizeArray(size); + var output = utils.parseGPTSingleSizeArray(size); assert.equal(output,undefined); }); }); From c0ad46dc0418c54ef82a096189ae3461c7b17271 Mon Sep 17 00:00:00 2001 From: hjeong12 Date: Thu, 7 Jan 2016 18:01:24 +0900 Subject: [PATCH 003/160] update --- gulpfile.js | 1 - test/spec/bids_spec.js | 188 ---------------------------------------- test/spec/utils_spec.js | 174 +++++++++++++++++++++++++++++++++++++ 3 files changed, 174 insertions(+), 189 deletions(-) delete mode 100644 test/spec/bids_spec.js diff --git a/gulpfile.js b/gulpfile.js index f18f08e4114..1b8c43bfabf 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -199,7 +199,6 @@ gulp.task("browserify", function() { "./test/test.js", "./test/spec/api_spec.js", "./test/spec/utils_spec.js", - "./test/spec/bids_spec.js", "./test/spec/adUnits_spec.js", "./test/spec/aliasBidder_spec.js" ]; diff --git a/test/spec/bids_spec.js b/test/spec/bids_spec.js deleted file mode 100644 index 7b64fbaedee..00000000000 --- a/test/spec/bids_spec.js +++ /dev/null @@ -1,188 +0,0 @@ - -describe("Publisher API _ Bids", function() { - var assert = require('chai').assert, - expect = require('chai').expect, - should = require('chai').should(); - - var prebid = require('../../src/prebid'); - - - var rightSlotCode = '/19968336/header-bid-tag-0'; - var rightDivCode = 'div-gpt-ad-1438287399331-0'; - var rightSlotSizes = [[300, 250], [300, 600]]; - - var topSlotCode = '/19968336/header-bid-tag1'; - var topDivCode = 'div-gpt-ad-1438287399331-1'; - var topSlotSizes = [[728, 90], [970, 90]]; - - - before(function(){ - - var adUnit1 = { - code: rightDivCode, - sizes: rightSlotSizes, - bids: [{ - bidder: 'appnexus', - params: { - placementId: '2251610' - } - }, - { - bidder: 'rubicon', - params: { - rp_account : '9707', - rp_site: '17955', - rp_zonesize : '50983-15', - rp_tracking : 'affiliate-1701207318', - rp_inventory : '{ deals : "mobv3_excl,atf,demo1849,csm1834,znexcl1,exunisite,exmars,extargt,ldacomp,ent19116,rn14858,ukent,g03070,qc12170,qc2690,qc2695,qc1988,asov1,qc12172,qc12169,qc27434,rn24858,ent29116,lngen,cntq,cntauto,anthea,smg_blklist,amnetctr,ntflxblk,amtblk,zentend,nortb,deschoeff,js,excltop," }', - rp_floor : '0.1' - } - }, - { - bidder: 'openx', - params: { - unit: 537245128, - pageURL : 'http://drudgereport.com', - refererURL : 'http://drudgereport.com', - jstag_url : 'http://ox-d.intermarkets.net/w/1.0/jstag' - } - }, - { - bidder: 'pubmatic', - params: { - publisherId: 39741, - adSlot: '39620189@300x250' - } - }, - { - bidder: 'criteo', - params: { - nid: '2612', - cookiename: 'cto_topix', - varname : 'crtg_content' - } - }, - { - bidder: 'casale', - params: { - slotId: 2, - casaleUrl: 'http://js.indexww.com/ht/elitedaily.js' - } - }, - { - bidder: 'casale', - params: { - slotId: 3 - } - }, - { - bidder: 'yieldbot', - params: { - pub: 'id', - name: 'name' - } - }, - { - bidder: 'amazon', - params: { - aId : 3080 - } - } - ] - }; //adUnit1 end - - var adUnit2 = { - code: topDivCode, - sizes: topSlotSizes, - bids: [{ - bidder: 'appnexus', - params: { - placementId : '5215561' - } - }] - }; - - var arr = [adUnit2, adUnit1]; - pbjs.addAdUnits([adUnit2, adUnit1]); - - }); - - after(function(){ - pbjs_testonly.clearAllAdUnits(); - }); - - it('Functions', function() { - - - }); - - // describe('Request and callback', function() { - - // it('bidsBackHandler callBack', function() { - - // pbjs.requestBids({ - // timeout: 500, - // bidsBackHandler : function(a){ - // assertTargeting(); - // assertArguments(a); - // } - // }); - - // var assertTargeting = function(){ - - // var top = pbjs.getAdserverTargetingForAdUnitCode(topDivCode), - // right = pbjs.getAdserverTargetingForAdUnitCode(rightDivCode), - // all = pbjs.getAdserverTargeting(); - - // console.log('top = ' + top); - // if(top!==undefined){ - // assert.typeOf(top,'object'); - // assert.typeOf(top.hb_adid,'string'); - // assert.typeOf(top.hb_bidder,'string'); - // assert.typeOf(top.hb_pb,'string'); - // assert.typeOf(top.hb_size,'string'); - // } - - - // console.log('right=' + right); - - // if(right!==undefined){ - // assert.typeOf(right,'object'); - // assert.typeOf(right.hb_adid,'string'); - // assert.typeOf(right.hb_bidder,'string'); - // assert.typeOf(right.hb_pb,'string'); - // assert.typeOf(right.hb_size,'string'); - // } - - // console.log('all = ' + all); - // assert.typeOf(all,'object'); - // should.exist(all[rightDivCode]); - // should.exist(all[topDivCode]); - - // assert.deepEqual(top,all[topDivCode],'top slot targeting'); - // assert.deepEqual(right,all[rightDivCode],'right slot targeting'); - // }; - - // var assertArguments = function(arg){ - // assert.typeOf(arg,'object'); - // should.exist(arg[rightDivCode]); - // should.exist(arg[topDivCode]); - - // console.log(arg); - // var responses = pbjs.getBidResponses(); - // console.log(responses); - // assert.typeOf(responses,'object'); - // should.exist(responses[rightDivCode]); - // should.exist(responses[topDivCode]); - // assert.deepEqual(arg,responses,'reponse object'); - - // var topResponse = pbjs.getBidResponsesForAdUnitCode(topDivCode); - // var rightResponse = pbjs.getBidResponsesForAdUnitCode(rightDivCode); - - // assert.deepEqual(topResponse,responses[topDivCode],'top slot response'); - // assert.deepEqual(rightResponse,responses[rightDivCode],'right slot response'); - - // }; - // }); - // }); -}); diff --git a/test/spec/utils_spec.js b/test/spec/utils_spec.js index ecd7f94fff6..d5a74610978 100755 --- a/test/spec/utils_spec.js +++ b/test/spec/utils_spec.js @@ -3,6 +3,18 @@ var utils = require('../../src/utils'); describe("Utils", function() { + var obj_string = 's', + obj_number = 1, + obj_object = {}, + obj_array = [], + obj_function = function(){}; + + var type_string = 'String', + type_number = 'Number', + type_object = 'Object', + type_array = 'Array', + type_function = 'Function'; + describe('replaceTokenInString', function(){ it('should replace all given tokens in a String', function() { @@ -222,4 +234,166 @@ describe("Utils", function() { assert.equal(output,undefined); }); }); + + describe('isA',function(){ + it('should return true with string object', function() { + var output = utils.isA(obj_string,type_string); + assert.deepEqual(output,true); + }); + + it('should return false with object', function() { + var output = utils.isA(obj_object,type_string); + assert.deepEqual(output,false); + }); + + it('should return true with object', function() { + var output = utils.isA(obj_object,type_object); + assert.deepEqual(output,true); + }); + + it('should return false with array object', function() { + var output = utils.isA(obj_array,type_object); + assert.deepEqual(output,false); + }); + + it('should return true with array object', function() { + var output = utils.isA(obj_array,type_array); + assert.deepEqual(output,true); + }); + + it('should return false with array object', function() { + var output = utils.isA(obj_array,type_function); + assert.deepEqual(output,false); + }); + + it('should return true with function', function() { + var output = utils.isA(obj_function,type_function); + assert.deepEqual(output,true); + }); + + it('should return false with number', function() { + var output = utils.isA(obj_function,type_number); + assert.deepEqual(output,false); + }); + + it('should return true with number', function() { + var output = utils.isA(obj_number,type_number); + assert.deepEqual(output,true); + }); + }); + + describe('isFn', function() { + it('should return true with input function',function(){ + var output = utils.isFn(obj_function); + assert.deepEqual(output,true); + }); + + it('should return false with input string',function(){ + var output = utils.isFn(obj_string); + assert.deepEqual(output,false); + }); + + it('should return false with input number',function(){ + var output = utils.isFn(obj_number); + assert.deepEqual(output,false); + }); + + it('should return false with input Array',function(){ + var output = utils.isFn(obj_array); + assert.deepEqual(output,false); + }); + + it('should return false with input object',function(){ + var output = utils.isFn(obj_object); + assert.deepEqual(output,false); + }); + }); + + describe('isStr',function(){ + it('should return true with input string',function(){ + var output = utils.isStr(obj_string); + assert.deepEqual(output,true); + }); + + it('should return false with input number',function(){ + var output = utils.isStr(obj_number); + assert.deepEqual(output,false); + }); + + it('should return false with input object',function(){ + var output = utils.isStr(obj_object); + assert.deepEqual(output,false); + }); + + it('should return false with input array',function(){ + var output = utils.isStr(obj_array); + assert.deepEqual(output,false); + }); + + it('should return false with input function',function(){ + var output = utils.isStr(obj_function); + assert.deepEqual(output,false); + }); + + }); + + describe('isArray',function(){ + it('should return false with input string',function(){ + var output = utils.isArray(obj_string); + assert.deepEqual(output,false); + }); + + it('should return false with input number',function(){ + var output = utils.isArray(obj_number); + assert.deepEqual(output,false); + }); + + it('should return false with input object',function(){ + var output = utils.isArray(obj_object); + assert.deepEqual(output,false); + }); + + it('should return true with input array',function(){ + var output = utils.isArray(obj_array); + assert.deepEqual(output,true); + }); + + it('should return false with input function',function(){ + var output = utils.isArray(obj_function); + assert.deepEqual(output,false); + }); + + }); + + + describe('isEmpty',function(){ + it('should return true with empty object',function(){ + var output = utils.isEmpty(obj_object); + assert.deepEqual(output,true); + }); + + it('should return false with non-empty object',function(){ + var obj = {'a':'b'}; + var output = utils.isEmpty(obj); + assert.deepEqual(output,false); + }); + + it('should return false with null',function(){ + var obj = null; + var output = utils.isEmpty(obj); + assert.deepEqual(output,true); + }); + }); + + describe('contains',function(){ + it('should return true if the input string contains in the input obj',function(){ + var output = utils.contains('123','1'); + assert.deepEqual(output,true); + }); + + it('should return false if the input string do not contain in the input obj',function(){ + var output = utils.contains('234','1'); + assert.deepEqual(output,false); + }); + }); }); From e42ec5f3b2008811f4c3e6d25ae287379ec9cfee Mon Sep 17 00:00:00 2001 From: Pieter de Zwart Date: Sun, 10 Jan 2016 12:37:19 -0800 Subject: [PATCH 004/160] Updating Rubicon adapter to use FastLane Javascript SDK --- src/adapters/rubicon.js | 404 ++++++++++++++++++++++------------------ 1 file changed, 219 insertions(+), 185 deletions(-) diff --git a/src/adapters/rubicon.js b/src/adapters/rubicon.js index 1462e53f266..6b5570c4a8a 100644 --- a/src/adapters/rubicon.js +++ b/src/adapters/rubicon.js @@ -1,188 +1,222 @@ -//Factory for creating the bidderAdaptor -var CONSTANTS = require('../constants.json'); -var utils = require('../utils.js'); -var bidfactory = require('../bidfactory.js'); -var bidmanager = require('../bidmanager.js'); - +/** + * @file Rubicon (Rubicon) adapter + */ +var utils = require('../utils'); +var bidmanager = require('../bidmanager'); +var bidfactory = require('../bidfactory'); +var adloader = require('../adloader'); + +/** + * @class RubiconAdapter + * Prebid adapter for Rubicon's Rubicon + */ var RubiconAdapter = function RubiconAdapter() { - // Map size dimensions to size 'ID' - var sizeMap = {}; - - function callBids(params) { - var bidArr = params.bids; - for (var i = 0; i < bidArr.length; i++) { - var bid = bidArr[i]; - //get the first size in the array - //TODO validation - var width = bid.sizes[0][0]; - var height = bid.sizes[0][1]; - var iframeContents = createRequestContent(bid, 'window.parent.pbjs.handleRubiconCallback', width, height); - var iframeId = loadIframeContent(iframeContents); - bid.iframeId = iframeId; - bidmanager.pbCallbackMap[getBidId(bid)] = bid; - } - - } - - // Build an ID that can be used to identify the response to the bid request. There - // may be an identifier we can send that gets sent back to us. - function getBidId(bid) { - return (bid.params ? [bid.params.rp_account, bid.params.rp_site, bid.params.rp_zonesize] : - [bid.account_id, bid.site_id, bid.zone_id, bid.size_id]).join('-'); - - } - - function loadIframeContent(content, callback) { - //create the iframe - var iframe = utils.createInvisibleIframe(); - var elToAppend = document.getElementsByTagName('head')[0]; - //insert the iframe into document - elToAppend.insertBefore(iframe, elToAppend.firstChild); - //todo make this more browser friendly - var iframeDoc = iframe.contentWindow.document; - iframeDoc.write(content); - iframeDoc.close(); - - return iframe.id; - - } - - function createRequestContent(bidOptions, callback, width, height) { - - // Map the size 'ID' to the dimensions - sizeMap[bidOptions.params.rp_zonesize.split('-')[1]] = { - width: width, - height: height - }; - - var content = 'inDapIF=true;'; - content += ''; - content += ''; - - - content += '' + - 'window.rp_account = "%%RP_ACCOUNT%%";' + - 'window.rp_site = "%%RP_SITE%%";' + - 'window.rp_zonesize = "%%RP_ZONESIZE%%";' + - 'window.rp_tracking = "%%RP_TRACKING%%";' + - 'window.rp_visitor = %%RP_VISITOR%%;' + - 'window.rp_width = %%RP_WIDTH%%;' + - 'window.rp_height = %%RP_HEIGHT%%;' + - 'window.rp_adtype = "jsonp";' + - 'window.rp_inventory = %%RP_INVENTORY%% ;' + - 'window.rp_floor=%%RP_FLOOR%%;' + - 'window.rp_fastlane = true;' + - 'window.rp_callback = ' + callback + ';'; - - - var map = {}; - map['RP_ACCOUNT'] = bidOptions.params.rp_account; - map['RP_SITE'] = bidOptions.params.rp_site; - map['RP_ZONESIZE'] = bidOptions.params.rp_zonesize; - map['RP_TRACKING'] = (bidOptions.params.rp_tracking) ? bidOptions.params.rp_tracking : ''; - map['RP_VISITOR'] = bidOptions.params.rp_visitor ? bidOptions.params.rp_visitor : '{}'; - map['RP_WIDTH'] = width; - map['RP_HEIGHT'] = height; - map['RP_INVENTORY'] = bidOptions.params.rp_inventory || '{}'; - map['RP_FLOOR'] = bidOptions.params.rp_floor ? bidOptions.params.rp_floor : '0.00'; - - content += ''; - content += ''; - content += ''; - - content = utils.replaceTokenInString(content, map, '%%'); - - //console.log(content); - - return content; - - } - - window.pbjs = window.pbjs || {que: []}; - window.pbjs.handleRubiconCallback = function(response) { - var placementCode = ''; - - var bid = {}; - if (response && response.status === 'ok') { - try { - var iframeId = ''; - var bidObj = bidmanager.getPlacementIdByCBIdentifer(getBidId(response)); - if (bidObj) { - placementCode = bidObj.placementCode; - bidObj.status = CONSTANTS.STATUS.GOOD; - iframeId = bidObj.iframeId; - } - - if (response.ads && response.ads[0] && response.ads[0].status === 'ok') { - bid = bidfactory.createBid(1); - - var rubiconAd = response.ads[0]; - var size = sizeMap[rubiconAd.size_id]; - var width = 0; - var height = 0; - - var iframeObj = window.frames[iframeId]; - var rubiconObj; - if(iframeObj.contentWindow){ - rubiconObj = iframeObj.contentWindow.RubiconAdServing - }else{ - rubiconObj = iframeObj.window.RubiconAdServing; - } - - if (rubiconObj && rubiconObj.AdSizes) { - /* should return - 1: { - dim: "468x60" - }, - */ - size = rubiconObj.AdSizes[rubiconAd.size_id]; - var sizeArray = size.dim.split('x'); - width = sizeArray[0]; - height = sizeArray[1]; - } - - bid.cpm = rubiconAd.cpm; - bid.ad = ''; - bid.ad_id = rubiconAd.ad_id; - bid.bidderCode = 'rubicon'; - bid.sizeId = rubiconAd.size_id; - bid.width = width; - bid.height = height; - - }else{ - bid = bidfactory.createBid(2); - bid.bidderCode = 'rubicon'; - var bidObj = bidmanager.getPlacementIdByCBIdentifer(getBidId(response)); - if (bidObj) { - placementCode = bidObj.placementCode; - } - } - - } catch (e) { - utils.logError('Error parsing rubicon response bid: ' + e.message); - } - - } else { - //set bid response code to 2 = no response or error - bid = bidfactory.createBid(2); - bid.bidderCode = 'rubicon'; - var bidObj = bidmanager.getPlacementIdByCBIdentifer(getBidId(response)); - if (bidObj) { - placementCode = bidObj.placementCode; - } - - } - - //add the bid response here - bidmanager.addBidResponse(placementCode, bid); - - }; - - return { - callBids: callBids - - }; - //end of Rubicon bid adaptor + var RUBICONTAG_URL = (window.location.protocol) + '//ads.rubiconproject.com/header/'; + var RUBICON_OK_STATUS = 'ok'; + var RUBICON_BIDDER_CODE = 'rubicon'; + var RUBICON_SIZE_MAP = { + "728x90": 2, + "160x600": 9, + "300x600": 10, + "300x250": 15, + "320x50": 43, + "300x1050": 54, + "970x250": 57 + }; + + // the fastlane creative code + var RUBICON_CREATIVE_START = ''; + + // pre-initialize the rubicon object + // needs to be attached to the window + window.rubicontag = window.rubicontag || {}; + window.rubicontag.cmd = window.rubicontag.cmd || []; + + // timestamp for logging + var _bidStart = null; + var bidCount = 0; + + /** + * Create an error bid + * @param {String} placement - the adunit path + * @param {Object} response - the (error) response from fastlane + * @return {Bid} a bid, for prebid + */ + function _errorBid(response, ads) { + var bidResponse = bidfactory.createBid(2); + bidResponse.bidderCode = RUBICON_BIDDER_CODE; + + // use the raw ads as the 'error' + bidResponse.error = ads; + return bidResponse; + } + + /** + * Sort function for CPM + * @param {Object} adA + * @param {Object} adB + * @return {Float} sort order value + */ + function _adCpmSort(adA, adB) { + return (adB.cpm || 0.0) - (adA.cpm || 0.0); + } + + /** + * Produce the code to render a creative + * @param {String} elemId the element passed to rubicon; this is essentially the ad-id + * @param {Array} size array of width, height + * @return {String} creative + */ + function _creative(elemId, size) { + + // convert the size to a rubicon sizeId + var sizeId = RUBICON_SIZE_MAP[size.join('x')]; + + if (!sizeId) { + utils.logError( + 'fastlane: missing sizeId for size: ' + size.join('x') + ' could not render creative', + RUBICON_BIDDER_CODE, RUBICON_SIZE_MAP); + return ''; + } + + return RUBICON_CREATIVE_START + elemId + '", "' + sizeId + RUBICON_CREATIVE_END; + } + + /** + * Create a (successful) bid for a unit, + * based on the given response + * @param {String} placement placement code/unit path + * @param {Object} response the response from rubicon + * @return {Bid} a bid objectj + */ + function _makeBid(response, ads) { + + // if there are multiple ads, sort by CPM + ads = ads.sort(_adCpmSort); + + var bidResponse = bidfactory.createBid(1), + ad = ads[0], + size = ad.dimensions; + + if (!size) { + // this really shouldn't happen + utils.logError('no dimensions given', RUBICON_BIDDER_CODE, ad); + return _errorBid(response, ads); + } + + bidResponse.bidderCode = RUBICON_BIDDER_CODE; + bidResponse.cpm = ad.cpm; + // the element id is what the iframe will use to render + // itself using the rubicontag.renderCreative API + bidResponse.ad = _creative(response.getElementId(), size); + bidResponse.width = size[0]; + bidResponse.height = size[1]; + return bidResponse; + } + + /** + * Add a success/error bid based + * on the response from rubicon + * @param {Object} response -- AJAX response from fastlane + */ + function _addBid(response, ads) { + + // get the bid for the placement code + var bid; + if (!ads || ads.length === 0) { + bid = _errorBid(response, ads); + } else { + bid = _makeBid(response, ads); + } + + bidmanager.addBidResponse(response.getSlotName(), bid); + } + + /** + * Helper to queue functions on rubicontag + * ready/available + * @param {Function} callback + */ + function _rready(callback) { + window.rubicontag.cmd.push(callback); + } + + /** + * download the rubicontag sdk + * @param {Object} options + * @param {String} options.accountId + * @param {Function} callback + */ + function _initSDK(options, done) { + var accountId = options.accountId; + adloader.loadScript(RUBICONTAG_URL + accountId + '.js', done); + } + + /** + * Define the slot using the rubicontag.defineSlot API + * @param {Object} Bidrequest + */ + function _defineSlot(bid) { + _rready(function () { + window.rubicontag.defineSlot({ + siteId: bid.params.siteId, + zoneId: bid.params.zoneId, + id: bid.placementCode, + sizes: bid.params.sizes + }); + }); + } + + /** + * Handle the bids received (from rubicon) + */ + function _bidsReady() { + // NOTE: we don't really need to do anything, + // because right now we're shimming XMLHttpRequest.open, + // but in the future we'll get data from rubicontag here + utils.logMessage('Rubicon Project bidding complete: ' + ((new Date).getTime() - _bidStart)); + + utils._each(rubicontag.getAllSlots(), function (slot) { + _addBid(slot, slot.getRawResponses()); + }); + } + + + /** + * Request the specified bids from + * Rubicon + * @param {Object} params the bidder-level params (from prebid) + * @param {Array} params.bids the bids requested + */ + function _callBids(params) { + + // start the timer; want to measure from + // even just loading the SDK + _bidStart = (new Date).getTime(); + + utils._each(params.bids, function (bid, index) { + // on the first bid, set up the SDK + // the config will be set on each bid + if (index === 0) { + _initSDK(bid.params); + } + + _defineSlot(bid); + }); + + _rready(function () { + window.rubicontag.run(_bidsReady); + }); + } + + return { + /** + * @public callBids + * the interface to Prebid + */ + callBids: _callBids + }; }; -module.exports = RubiconAdapter; +module.exports = RubiconAdapter; \ No newline at end of file From 6dd8be4c26d06bed38578b3c051e2f02075192f3 Mon Sep 17 00:00:00 2001 From: hjeong12 Date: Mon, 11 Jan 2016 22:53:07 +0900 Subject: [PATCH 005/160] add unit test --- test/spec/utils_spec.js | 76 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/test/spec/utils_spec.js b/test/spec/utils_spec.js index d5a74610978..73d5ae7113a 100755 --- a/test/spec/utils_spec.js +++ b/test/spec/utils_spec.js @@ -396,4 +396,80 @@ describe("Utils", function() { assert.deepEqual(output,false); }); }); + + describe('_map',function(){ + it('return empty array when input object is empty',function(){ + var input = {}; + var callback = function(){}; + var output = utils._map(input,callback); + assert.deepEqual(output,[]); + }); + + it('return value array with vaild input object',function(){ + var input = { 'a':'A','b':'B'}; + var callback = function(v){return v;}; + var output = utils._map(input,callback); + assert.deepEqual(output,['A','B']); + }); + + it('return value array with vaild input object_callback func changed 1',function(){ + var input = { 'a':'A','b':'B'}; + var callback = function(v,k){return v+k;}; + var output = utils._map(input,callback); + assert.deepEqual(output,['Aa','Bb']); + }); + + it('return value array with vaild input object_callback func changed 2',function(){ + var input = { 'a':'A','b':'B'}; + var callback = function(v,k,o){return o;}; + var output = utils._map(input,callback); + assert.deepEqual(output,[input,input]); + }); + }); + + describe('createInvisibleIframe',function(){ + var output = utils.createInvisibleIframe(); + + it('return iframe - id',function(){ + assert.ok(output.id); + }); + it('return iframe - height',function(){ + assert.deepEqual(output.height,0); + }); + it('return iframe - width',function(){ + assert.deepEqual(output.width,0); + }); + it('return iframe - border',function(){ + assert.deepEqual(output.border,'0px'); + }); + it('return iframe - hspace',function(){ + assert.deepEqual(output.hspace,'0'); + }); + it('return iframe - vspace',function(){ + assert.deepEqual(output.vspace,'0'); + }); + it('return iframe - marginWidth',function(){ + assert.deepEqual(output.marginWidth,'0'); + }); + it('return iframe - marginHeight',function(){ + assert.deepEqual(output.marginHeight,'0'); + }); + it('return iframe - style.border',function(){ + assert.deepEqual(output.style.border,'0px'); + }); + it('return iframe - scrolling',function(){ + assert.deepEqual(output.scrolling,'no'); + }); + it('return iframe - frameBorder',function(){ + assert.deepEqual(output.frameBorder,'0'); + }); + it('return iframe - src',function(){ + assert.deepEqual(output.src,'about:self'); + }); + it('return iframe - style',function(){ + assert.ok(output.style); + }); + }); + + }); From 67df153a5279217d7ba92c1f8835a75b0d87676b Mon Sep 17 00:00:00 2001 From: David Babaioff Date: Mon, 1 Feb 2016 17:43:37 +0200 Subject: [PATCH 006/160] Avoid global variable --- src/adapters/sovrn.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/adapters/sovrn.js b/src/adapters/sovrn.js index bafee8a2bc3..85df2b382cd 100644 --- a/src/adapters/sovrn.js +++ b/src/adapters/sovrn.js @@ -62,7 +62,7 @@ var SovrnAdapter = function SovrnAdapter() { adW=bid.sizes[0][0]; adH=bid.sizes[0][1]; } - imp = + var imp = { id: utils.getUniqueIdentifierStr(), banner: { From fec58d1314a154cba2b6f69474edce98d30c5bf6 Mon Sep 17 00:00:00 2001 From: protonate Date: Wed, 3 Feb 2016 09:57:01 -0800 Subject: [PATCH 007/160] Update README with supported browser info --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 12eba8789bb..d35f347337e 100644 --- a/README.md +++ b/README.md @@ -112,3 +112,5 @@ Navigate to http://localhost:9999/integrationExamples/gpt/pbjs_example_gpt.html Navigate to http://localhost:9999/test/spec/runner.html to run the test file. +### Supported Browsers ### +Prebid.js is supported on IE9+ and modern browsers. \ No newline at end of file From 01efd3cabee2a63f5703e323d7b8229bc854c7a9 Mon Sep 17 00:00:00 2001 From: protonate Date: Mon, 1 Feb 2016 14:17:26 -0800 Subject: [PATCH 008/160] Migrate build system to Webpack, using Karma to run tests and Istanbul for coverage reporting. * restore `serve` gulp task * remove deprecated test runner files * add a stub for unit test of events.js * add additional test for utils.js * include html reporter for test results * run test task on watch updates * remove sauce labs karma config * comment out one test (temporary) * update readme with path to test results html report * run webpack task on watch updates * clean up karma.conf * update events.js unit test * modify .gitignore * update example paths * use utils._map in place of ES5 Object.keys and map --- .gitignore | 7 + .jshintrc | 33 + README.md | 2 +- dist/prebid.js | 4122 ------------- dist/prebid.min.js | 4 - gulpHelpers.js | 14 + gulpfile.js | 259 +- .../gpt/gpt_aliasingBidder.html | 2 +- integrationExamples/gpt/pbjs_example_gpt.html | 2 +- .../gpt/pbjs_partial_refresh_gpt.html | 2 +- karma.conf.js | 110 + package.json | 95 +- src/prebid.js | 5 +- test/automatedRunnner.html | 29 - test/lib/browser/jquery.js | 4 - test/lib/browser/mocha.css | 199 - test/lib/chai.js | 5332 ----------------- test/lib/mocha.js | 4597 -------------- test/runner.html | 33 - test/spec/unit/events_spec.js | 27 + test/spec/utils_spec.js | 15 +- test/test.js | 316 - test/utilsTest.js | 28 - webpack.conf.js | 23 + 24 files changed, 385 insertions(+), 14875 deletions(-) create mode 100644 .jshintrc delete mode 100644 dist/prebid.js delete mode 100644 dist/prebid.min.js create mode 100644 gulpHelpers.js create mode 100644 karma.conf.js delete mode 100644 test/automatedRunnner.html delete mode 100755 test/lib/browser/jquery.js delete mode 100755 test/lib/browser/mocha.css delete mode 100644 test/lib/chai.js delete mode 100755 test/lib/mocha.js delete mode 100755 test/runner.html create mode 100644 test/spec/unit/events_spec.js delete mode 100644 test/test.js delete mode 100644 test/utilsTest.js create mode 100644 webpack.conf.js diff --git a/.gitignore b/.gitignore index dc38ee1160a..d0603ef103b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,12 @@ +# Built Files node_modules/ +build +# Test Files +test/app + +# Dev File +integrationExamples/gpt/gpt.html # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 00000000000..8b366353381 --- /dev/null +++ b/.jshintrc @@ -0,0 +1,33 @@ +{ + "bitwise": true, + "browser": true, + "curly": false, + "devel": true, + "eqeqeq": true, + "es3": true, + "freeze": true, + "immed": true, + "maxdepth": 5, + "newcap": true, + "noarg": true, + "node": true, + "notypeof": true, + "trailing": true, + "undef": true, + "unused": true, + "scripturl": true, + "globals": { + "assert": false, + "mediation": false, + "expect": false, + "dump": false, + "describe": false, + "it": false, + "xit": false, + "pkg": false, + "sinon": false, + "beforeEach": false, + "afterEach": false, + "APN": false + } +} diff --git a/README.md b/README.md index d35f347337e..edf19edc35f 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,7 @@ Navigate to http://localhost:9999/integrationExamples/gpt/pbjs_example_gpt.html ### Unit Test In the Browser ### -Navigate to http://localhost:9999/test/spec/runner.html to run the test file. +Navigate to http://localhost:9999/build/coverage/karma_html/report to view test results. ### Supported Browsers ### Prebid.js is supported on IE9+ and modern browsers. \ No newline at end of file diff --git a/dist/prebid.js b/dist/prebid.js deleted file mode 100644 index c4fa62de42b..00000000000 --- a/dist/prebid.js +++ /dev/null @@ -1,4122 +0,0 @@ -/* Prebid.js v0.5.0 -Updated : 2016-01-11 */ -(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o= 0) { - this.timeoutDelay = timeoutDelay; - } - - this.siteID = siteID; - this.impressions = []; - this._parseFnName = undefined; - if (top === self) { - this.sitePage = location.href; - this.topframe = 1; - } else { - this.sitePage = document.referrer; - this.topframe = 0; - } - if (typeof parseFn !== 'undefined') { - if (typeof parseFn === 'function') { - this._parseFnName = "cygnus_index_args.parseFn"; - } else { - throw "Invalid jsonp target function"; - } - } - if (typeof _IndexRequestData.requestCounter === 'undefined') { - _IndexRequestData.requestCounter = Math.floor(Math.random() * 256); - } else { - _IndexRequestData.requestCounter = (_IndexRequestData.requestCounter + 1) % 256; - } - this.requestID = String((new Date().getTime() % 2592000) * 256 + _IndexRequestData.requestCounter + 256); - this.initialized = true; - } - OpenRTBRequest.prototype.serialize = function() { - var json = '{"id":' + this.requestID + ',"site":{"page":"' + quote(this.sitePage) + '"'; - if (typeof document.referrer === 'string') { - json += ',"ref":"' + quote(document.referrer) + '"'; - } - json += '},"imp":['; - for (var i = 0; i < this.impressions.length; i++) { - var impObj = this.impressions[i]; - var ext = []; - json += '{"id":"' + impObj.id + '", "banner":{"w":' + impObj.w + ',"h":' + impObj.h + ',"topframe":' + String(this.topframe) + "}"; - if (typeof impObj.bidfloor === 'number') { - json += ',"bidfloor":' + impObj.bidfloor; - if (typeof impObj.bidfloorcur === 'string') { - json += ',"bidfloorcur":"' + quote(impObj.bidfloorcur) + '"'; - } - } - if (typeof impObj.slotID === 'string' && (!impObj.slotID.match(/^\s*$/))) { - ext.push('"sid":"' + quote(impObj.slotID) + '"'); - } - if (typeof impObj.siteID === 'number') { - ext.push('"siteID":' + impObj.siteID); - } - if (ext.length > 0) { - json += ',"ext": {' + ext.join() + '}'; - } - if (i + 1 == this.impressions.length) { - json += '}'; - } else { - json += '},'; - } - } - json += "]}"; - return json; - }; - OpenRTBRequest.prototype.setPageOverride = function(sitePageOverride) { - if (typeof sitePageOverride === 'string' && (!sitePageOverride.match(/^\s*$/))) { - this.sitePage = sitePageOverride; - return true; - } else { - return false; - } - }; - OpenRTBRequest.prototype.addImpression = function(width, height, bidFloor, bidFloorCurrency, slotID, siteID) { - var impObj = { - 'id': String(this.impressions.length + 1) - }; - if (typeof width !== 'number' || width <= 1) { - return null; - } - if (typeof height !== 'number' || height <= 1) { - return null; - } - if ((typeof slotID === 'string' || typeof slotID === 'number') && String(slotID).length <= 50) { - impObj.slotID = String(slotID); - } - impObj.w = width; - impObj.h = height; - if (bidFloor !== undefined && typeof bidFloor !== 'number') { - return null; - } - if (typeof bidFloor === 'number') { - if (bidFloor < 0) { - return null; - } - impObj.bidfloor = bidFloor; - if (bidFloorCurrency !== undefined && typeof bidFloorCurrency !== 'string') { - return null; - } - impObj.bidfloorcur = bidFloorCurrency; - } - if (typeof siteID !== 'undefined') { - if (typeof siteID === 'number' && siteID % 1 === 0 && siteID >= 0) { - impObj.siteID = siteID; - } else { - return null; - } - } - this.impressions.push(impObj); - return impObj.id; - }; - - OpenRTBRequest.prototype.buildRequest = function() { - if (this.impressions.length === 0 || this.initialized !== true) { - return; - } - var jsonURI = encodeURIComponent(this.serialize()); - var scriptSrc = window.location.protocol === 'https:' ? 'https://as-sec.casalemedia.com' : 'http://as.casalemedia.com'; - scriptSrc += '/headertag?v=9&x3=1&fn=cygnus_index_parse_res&s=' + this.siteID + '&r=' + jsonURI; - if (typeof this.timeoutDelay === "number" && this.timeoutDelay % 1 === 0 && this.timeoutDelay >= 0) { - scriptSrc += '&t=' + this.timeoutDelay; - } - return scriptSrc; - }; - try { - if (typeof cygnus_index_args === 'undefined' || typeof cygnus_index_args.siteID === 'undefined' || typeof cygnus_index_args.slots === 'undefined') { - return; - } - if (typeof _IndexRequestData === 'undefined') { - _IndexRequestData = {}; - _IndexRequestData.impIDToSlotID = {}; - _IndexRequestData.reqOptions = {}; - } - var req = new OpenRTBRequest(cygnus_index_args.siteID, cygnus_index_args.parseFn, cygnus_index_args.timeout); - if (cygnus_index_args.url && typeof cygnus_index_args.url === 'string') { - req.setPageOverride(cygnus_index_args.url); - } - _IndexRequestData.impIDToSlotID[req.requestID] = {}; - _IndexRequestData.reqOptions[req.requestID] = {}; - var slotDef, impID; - - for (var i = 0; i < cygnus_index_args.slots.length; i++) { - slotDef = cygnus_index_args.slots[i]; - - impID = req.addImpression(slotDef.width, slotDef.height, slotDef.bidfloor, slotDef.bidfloorcur, slotDef.id, slotDef.siteID); - if (impID) { - _IndexRequestData.impIDToSlotID[req.requestID][impID] = String(slotDef.id); - } - } - if (typeof cygnus_index_args.targetMode === 'number') { - _IndexRequestData.reqOptions[req.requestID].targetMode = cygnus_index_args.targetMode; - } - if (typeof cygnus_index_args.callback === 'function') { - _IndexRequestData.reqOptions[req.requestID].callback = cygnus_index_args.callback; - } - return req.buildRequest(); - } catch (e) {} -}; - -var IndexExchangeAdapter = function IndexExchangeAdapter() { - var slotIdMap = {}; - var requiredParams = [ - /* 0 */ - 'id', - /* 1 */ - 'siteID' - ]; - var firstAdUnitCode = ''; - - function _callBids(request) { - var bidArr = request.bids; - - if (!utils.hasValidBidRequest(bidArr[0].params, requiredParams, ADAPTER_NAME)) { - return; - } - - cygnus_index_args.slots = []; - var bidCount = 0; - - //Grab the slot level data for cygnus_index_args - for (i = 0; i < bidArr.length; i++) { - var bid = bidArr[i]; - - var width; - var height; - - outer: for (var j = 0; j < bid.sizes.length; j++) { - inner: for (var k = 0; k < cygnus_index_adunits.length; k++) { - if (bid.sizes[j][0] === cygnus_index_adunits[k][0] && - bid.sizes[j][1] === cygnus_index_adunits[k][1]) { - width = bid.sizes[j][0]; - height = bid.sizes[j][1]; - break outer; - } - } - } - - if (bid.params.timeout && typeof cygnus_index_args.timeout === 'undefined') { - cygnus_index_args.timeout = bid.params.timeout; - } - - if (bid.params.siteID && typeof cygnus_index_args.siteID === 'undefined') { - cygnus_index_args.siteID = bid.params.siteID; - } - - if (bid.params.sqps && typeof cygnus_index_args.SQPS === 'undefined') { - cygnus_index_args.slots.push({ - id:"SPQS", - width: bid.params.sqps.width, - height: bid.params.sqps.height, - siteID: bid.params.sqps.siteID || cygnus_index_args.siteID - }); - } - - if (utils.hasValidBidRequest(bid.params, requiredParams, ADAPTER_NAME)) { - firstAdUnitCode = bid.placementCode; - var slotId = bid.params[requiredParams[0]]; - slotIdMap[slotId] = bid; - - if (cygnus_index_primary_request) { - cygnus_index_args.slots.push({ - id: bid.params.id, - width: width, - height: height, - siteID: bid.params.siteID || cygnus_index_args.siteID - }); - - bidCount++; - - if (bid.params.tier2SiteID) { - cygnus_index_args.slots.push({ - id: "T1_"+bid.params.id, - width: width, - height: height, - siteID: bid.params.tier2SiteID - }); - } - if (bid.params.tier3SiteID) { - cygnus_index_args.slots.push({ - id:"T2_"+bid.params.id, - width:width, - height:height, - siteID:bid.params.tier3SiteID - }); - } - } - } - bidmanager.setExpectedBidsCount(ADAPTER_CODE, bidCount); - } - cygnus_index_primary_request = false; - - adloader.loadScript(cygnus_index_start()); - - window.cygnus_index_ready_state = function() { - try { - var indexObj = _IndexRequestData.targetIDToBid; - var lookupObj = cygnus_index_args; - - if (utils.isEmpty(indexObj)) { - var bid = bidfactory.createBid(2); - bid.bidderCode = ADAPTER_CODE; - logErrorBidResponse(); - return; - } - - utils._each(indexObj, function(adContents, cpmAndSlotId) { - utils._each(slotIdMap, function(bid, adSlotId) { - var obj = cpmAndSlotId.split('_'); - var currentId = obj[0]; - var currentCPM = obj[1]; - if (currentId === adSlotId) { - var bidObj = slotIdMap[adSlotId]; - var adUnitCode = bidObj.placementCode; - var slotObj = getSlotObj(cygnus_index_args, adSlotId); - - bid = bidfactory.createBid(1); - bid.cpm = currentCPM / 100; - bid.ad = adContents[0]; - bid.ad_id = adSlotId; - bid.bidderCode = ADAPTER_CODE; - bid.width = slotObj.width; - bid.height = slotObj.height; - bid.siteID = slotObj.siteID; - - bidmanager.addBidResponse(adUnitCode, bid); - } - }); - }); - } catch (e) { - utils.logError('Error calling index adapter', ADAPTER_NAME, e); - logErrorBidResponse(); - } - }; - } - - function getSlotObj(obj, id) { - var arr = obj.slots; - var returnObj = {}; - utils._each(arr, function(value) { - if (value.id === id) { - returnObj = value; - } - }); - return returnObj; - } - - function logErrorBidResponse() { - //no bid response - var bid = bidfactory.createBid(2); - bid.bidderCode = ADAPTER_CODE; - //log error to first add unit - bidmanager.addBidResponse(firstAdUnitCode, bid); - } - - return { - callBids: _callBids - }; - //end of Rubicon bid adaptor -}; - -module.exports = IndexExchangeAdapter; - -},{"../adloader.js":13,"../bidfactory.js":14,"../bidmanager.js":15,"../constants.json":16,"../utils.js":20}],7:[function(require,module,exports){ -var CONSTANTS = require('../constants.json'); -var utils = require('../utils.js'); -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; - } - if (bid.params.refererURL) { - opts.refererURL = bid.params.refererURL; - } - if (bid.params.jstag_url) { - scriptUrl = bid.params.jstag_url; - } - if (bid.params.pgid) { - opts.pgid = bid.params.pgid; - } - } - _requestBids(); - } - - function _requestBids() { - - if (scriptUrl) { - adloader.loadScript(scriptUrl, function() { - var i; - var POX = OX(); - - POX.setPageURL(opts.pageURL); - POX.setRefererURL(opts.refererURL); - POX.addPage(opts.pgid); - - // Add each ad unit ID - for (i = 0; i < bids.length; i++) { - POX.addAdUnit(bids[i].params.unit); - } - - 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'); - 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(); - }); - } - } - - return { - callBids: _callBids - }; -}; - -module.exports = OpenxAdapter; -},{"../adloader":13,"../bidfactory.js":14,"../bidmanager.js":15,"../constants.json":16,"../utils.js":20}],8:[function(require,module,exports){ -var CONSTANTS = require('../constants.json'); -var utils = require('../utils.js'); -var bidfactory = require('../bidfactory.js'); -var bidmanager = require('../bidmanager.js'); -var adloader = require('../adloader'); - -/** - * Adapter for requesting bids from Pubmatic. - * - * @returns {{callBids: _callBids}} - * @constructor - */ -var PubmaticAdapter = function PubmaticAdapter() { - - var bids; - var _pm_pub_id; - var _pm_optimize_adslots = []; - - function _callBids(params) { - bids = params.bids; - for (var i = 0; i < bids.length; i++) { - var bid = bids[i]; - bidmanager.pbCallbackMap['' + bid.params.adSlot] = bid; - _pm_pub_id = _pm_pub_id || bid.params.publisherId; - _pm_optimize_adslots.push(bid.params.adSlot); - } - - // Load pubmatic script in an iframe, because they call document.write - _getBids(); - } - - function _getBids() { - - // required variables for pubmatic pre-bid call - window.pm_pub_id = _pm_pub_id; - window.pm_optimize_adslots = _pm_optimize_adslots; - - //create the iframe - var iframe = utils.createInvisibleIframe(); - var elToAppend = document.getElementsByTagName('head')[0]; - //insert the iframe into document - elToAppend.insertBefore(iframe, elToAppend.firstChild); - //todo make this more browser friendly - var iframeDoc = iframe.contentWindow.document; - iframeDoc.write(_createRequestContent()); - iframeDoc.close(); - } - - function _createRequestContent() { - var content = 'inDapIF=true;'; - content += ''; - content += ''; - content += '' + - 'window.pm_pub_id = "%%PM_PUB_ID%%";' + - 'window.pm_optimize_adslots = [%%PM_OPTIMIZE_ADSLOTS%%];'; - content += ''; - - var map = {}; - map['PM_PUB_ID'] = _pm_pub_id; - map['PM_OPTIMIZE_ADSLOTS'] = _pm_optimize_adslots.map(function(adSlot) { - return "'" + adSlot + "'"; - }).join(','); - - content += ''; - content += ''; - content += 'window.parent.pbjs.handlePubmaticCallback({progKeyValueMap: progKeyValueMap, bidDetailsMap: bidDetailsMap})'; - content += ''; - content += ''; - content = utils.replaceTokenInString(content, map, '%%'); - - return content; - } - - pbjs.handlePubmaticCallback = function(response) { - var i; - var adUnit; - var adUnitInfo; - var bid; - var bidResponseMap = (response && response.bidDetailsMap) || {}; - var bidInfoMap = (response && response.progKeyValueMap) || {}; - var dimensions; - - for (i = 0; i < bids.length; i++) { - var adResponse; - bid = bids[i].params; - - adUnit = bidResponseMap[bid.adSlot] || {}; - - // adUnitInfo example: bidstatus=0;bid=0.0000;bidid=39620189@320x50;wdeal= - adUnitInfo = (bidInfoMap[bid.adSlot] || '').split(';').reduce(function(result, pair) { - var parts = pair.split('='); - result[parts[0]] = parts[1]; - return result; - }, {}); - - if (adUnitInfo.bidstatus === '1') { - dimensions = adUnitInfo.bidid.split('@')[1].split('x'); - adResponse = bidfactory.createBid(1); - adResponse.bidderCode = 'pubmatic'; - adResponse.adSlot = bid.adSlot; - adResponse.cpm = Number(adUnitInfo.bid); - adResponse.ad = unescape(adUnit.creative_tag); - adResponse.adUrl = unescape(adUnit.tracking_url); - adResponse.width = dimensions[0]; - adResponse.height = dimensions[1]; - adResponse.dealId = adUnitInfo.wdeal; - - bidmanager.addBidResponse(bids[i].placementCode, adResponse); - } else { - // Indicate an ad was not returned - adResponse = bidfactory.createBid(2); - adResponse.bidderCode = 'pubmatic'; - bidmanager.addBidResponse(bids[i].placementCode, adResponse); - } - } - }; - - return { - callBids: _callBids - }; - -}; - -module.exports = PubmaticAdapter; -},{"../adloader":13,"../bidfactory.js":14,"../bidmanager.js":15,"../constants.json":16,"../utils.js":20}],9:[function(require,module,exports){ -var bidfactory = require('../bidfactory.js'); -var bidmanager = require('../bidmanager.js'); -var adloader = require('../adloader.js'); - -var PulsePointAdapter = function PulsePointAdapter() { - - var getJsStaticUrl = 'http://tag.contextweb.com/getjs.static.js'; - var bidUrl = 'http://tag.contextweb.com/bid'; - - function _callBids(params) { - if(typeof window.pp === 'undefined') { - adloader.loadScript(getJsStaticUrl, function() { bid(params); }); - } else { - bid(params); - } - } - - function bid(params) { - var bids = params.bids; - for (var i = 0; i < bids.length; i++) { - var bidRequest = bids[i]; - var callback = bidResponseCallback(bidRequest); - var ppBidRequest = new window.pp.Ad({ - cf : bidRequest.params.cf, - cp : bidRequest.params.cp, - ct : bidRequest.params.ct, - cn : 1, - ca : window.pp.requestActions.BID, - cu : bidUrl, - adUnitId: bidRequest.placementCode, - callback: callback - }); - ppBidRequest.display(); - } - } - - function bidResponseCallback(bid) { - return function(bidResponse) { - bidResponseAvailable(bid, bidResponse); - }; - } - - function bidResponseAvailable(bidRequest, bidResponse) { - if(bidResponse) { - var adSize = bidRequest.params.cf.toUpperCase().split('X'); - var bid = bidfactory.createBid(1); - bid.bidderCode = bidRequest.bidder; - bid.cpm = bidResponse.bidCpm; - bid.ad = bidResponse.html; - bid.width = adSize[0]; - bid.height = adSize[1]; - bidmanager.addBidResponse(bidRequest.placementCode, bid); - } else { - var passback = bidfactory.createBid(2); - passback.bidderCode = bidRequest.bidder; - bidmanager.addBidResponse(bidRequest.placementCode, passback); - } - } - - return { - callBids: _callBids - }; - -}; - -module.exports = PulsePointAdapter; - -},{"../adloader.js":13,"../bidfactory.js":14,"../bidmanager.js":15}],10:[function(require,module,exports){ -//Factory for creating the bidderAdaptor -var CONSTANTS = require('../constants.json'); -var utils = require('../utils.js'); -var bidfactory = require('../bidfactory.js'); -var bidmanager = require('../bidmanager.js'); - -var RubiconAdapter = function RubiconAdapter() { - // Map size dimensions to size 'ID' - var sizeMap = {}; - - function callBids(params) { - var bidArr = params.bids; - for (var i = 0; i < bidArr.length; i++) { - var bid = bidArr[i]; - //get the first size in the array - //TODO validation - var width = bid.sizes[0][0]; - var height = bid.sizes[0][1]; - var iframeContents = createRequestContent(bid, 'window.parent.pbjs.handleRubiconCallback', width, height); - var iframeId = loadIframeContent(iframeContents); - bid.iframeId = iframeId; - bidmanager.pbCallbackMap[getBidId(bid)] = bid; - } - - } - - // Build an ID that can be used to identify the response to the bid request. There - // may be an identifier we can send that gets sent back to us. - function getBidId(bid) { - return (bid.params ? [bid.params.rp_account, bid.params.rp_site, bid.params.rp_zonesize] : - [bid.account_id, bid.site_id, bid.zone_id, bid.size_id]).join('-'); - - } - - function loadIframeContent(content, callback) { - //create the iframe - var iframe = utils.createInvisibleIframe(); - var elToAppend = document.getElementsByTagName('head')[0]; - //insert the iframe into document - elToAppend.insertBefore(iframe, elToAppend.firstChild); - //todo make this more browser friendly - var iframeDoc = iframe.contentWindow.document; - iframeDoc.write(content); - iframeDoc.close(); - - return iframe.id; - - } - - function createRequestContent(bidOptions, callback, width, height) { - - // Map the size 'ID' to the dimensions - sizeMap[bidOptions.params.rp_zonesize.split('-')[1]] = { - width: width, - height: height - }; - - var content = 'inDapIF=true;'; - content += ''; - content += ''; - - - content += '' + - 'window.rp_account = "%%RP_ACCOUNT%%";' + - 'window.rp_site = "%%RP_SITE%%";' + - 'window.rp_zonesize = "%%RP_ZONESIZE%%";' + - 'window.rp_tracking = "%%RP_TRACKING%%";' + - 'window.rp_visitor = %%RP_VISITOR%%;' + - 'window.rp_width = %%RP_WIDTH%%;' + - 'window.rp_height = %%RP_HEIGHT%%;' + - 'window.rp_adtype = "jsonp";' + - 'window.rp_inventory = %%RP_INVENTORY%% ;' + - 'window.rp_floor=%%RP_FLOOR%%;' + - 'window.rp_fastlane = true;' + - 'window.rp_callback = ' + callback + ';'; - - - var map = {}; - map['RP_ACCOUNT'] = bidOptions.params.rp_account; - map['RP_SITE'] = bidOptions.params.rp_site; - map['RP_ZONESIZE'] = bidOptions.params.rp_zonesize; - map['RP_TRACKING'] = (bidOptions.params.rp_tracking) ? bidOptions.params.rp_tracking : ''; - map['RP_VISITOR'] = bidOptions.params.rp_visitor ? bidOptions.params.rp_visitor : '{}'; - map['RP_WIDTH'] = width; - map['RP_HEIGHT'] = height; - map['RP_INVENTORY'] = bidOptions.params.rp_inventory || '{}'; - map['RP_FLOOR'] = bidOptions.params.rp_floor ? bidOptions.params.rp_floor : '0.00'; - - content += ''; - content += ''; - content += ''; - - content = utils.replaceTokenInString(content, map, '%%'); - - //console.log(content); - - return content; - - } - - window.pbjs = window.pbjs || {que: []}; - window.pbjs.handleRubiconCallback = function(response) { - var placementCode = ''; - - var bid = {}; - if (response && response.status === 'ok') { - try { - var iframeId = ''; - var bidObj = bidmanager.getPlacementIdByCBIdentifer(getBidId(response)); - if (bidObj) { - placementCode = bidObj.placementCode; - bidObj.status = CONSTANTS.STATUS.GOOD; - iframeId = bidObj.iframeId; - } - - if (response.ads && response.ads[0] && response.ads[0].status === 'ok') { - bid = bidfactory.createBid(1); - - var rubiconAd = response.ads[0]; - var size = sizeMap[rubiconAd.size_id]; - var width = 0; - var height = 0; - - var iframeObj = window.frames[iframeId]; - var rubiconObj; - if(iframeObj.contentWindow){ - rubiconObj = iframeObj.contentWindow.RubiconAdServing - }else{ - rubiconObj = iframeObj.window.RubiconAdServing; - } - - if (rubiconObj && rubiconObj.AdSizes) { - /* should return - 1: { - dim: "468x60" - }, - */ - size = rubiconObj.AdSizes[rubiconAd.size_id]; - var sizeArray = size.dim.split('x'); - width = sizeArray[0]; - height = sizeArray[1]; - } - - bid.cpm = rubiconAd.cpm; - bid.ad = ''; - bid.ad_id = rubiconAd.ad_id; - bid.bidderCode = 'rubicon'; - bid.sizeId = rubiconAd.size_id; - bid.width = width; - bid.height = height; - - }else{ - bid = bidfactory.createBid(2); - bid.bidderCode = 'rubicon'; - var bidObj = bidmanager.getPlacementIdByCBIdentifer(getBidId(response)); - if (bidObj) { - placementCode = bidObj.placementCode; - } - } - - } catch (e) { - utils.logError('Error parsing rubicon response bid: ' + e.message); - } - - } else { - //set bid response code to 2 = no response or error - bid = bidfactory.createBid(2); - bid.bidderCode = 'rubicon'; - var bidObj = bidmanager.getPlacementIdByCBIdentifer(getBidId(response)); - if (bidObj) { - placementCode = bidObj.placementCode; - } - - } - - //add the bid response here - bidmanager.addBidResponse(placementCode, bid); - - }; - - return { - callBids: callBids - - }; - //end of Rubicon bid adaptor -}; - -module.exports = RubiconAdapter; - -},{"../bidfactory.js":14,"../bidmanager.js":15,"../constants.json":16,"../utils.js":20}],11:[function(require,module,exports){ -var CONSTANTS = require('../constants.json'); -var utils = require('../utils.js'); -var bidfactory = require('../bidfactory.js'); -var bidmanager = require('../bidmanager.js'); -var adloader = require('../adloader'); - -var defaultPlacementForBadBid = ''; - -/** - * Adapter for requesting bids from Sovrn - */ -var SovrnAdapter = function SovrnAdapter() { - var sovrnUrl = 'ap.lijit.com/rtb/bid'; - - function _callBids(params) { - var sovrnBids = params.bids || []; - // De-dupe by tagid then issue single bid request for all bids - _requestBids(_getUniqueTagids(sovrnBids)); - } - - // filter bids to de-dupe them? - function _getUniqueTagids(bids) { - var key; - var map = {}; - var Tagids = []; - bids.forEach(function(bid) { - map[utils.getBidIdParamater('tagid', bid.params)] = bid; - }); - for (key in map) { - if (map.hasOwnProperty(key)) { - Tagids.push(map[key]); - } - } - return Tagids; - } - - function _requestBids(bidReqs) { - // build bid request object - var domain = window.location.host; - var page = window.location.pathname + location.search + location.hash; - - var sovrnImps = []; - //assign the first adUnit (placement) for bad bids; - defaultPlacementForBadBid = bidReqs[0].placementCode; - - //build impression array for sovrn - utils._each(bidReqs, function(bid) - { - var tagId = utils.getBidIdParamater('tagid', bid.params); - var bidFloor = utils.getBidIdParamater('bidfloor', bid.params); - var adW=0,adH=0; - - //sovrn supports only one size per tagid, so we just take the first size if there are more - //if we are a 2 item array of 2 numbers, we must be a SingleSize array - var sizeArrayLength = bid.sizes.length; - if (sizeArrayLength === 2 && typeof bid.sizes[0] === 'number' && typeof bid.sizes[1] === 'number') { - adW=bid.sizes[0]; - adH=bid.sizes[1]; - } - else - { - adW=bid.sizes[0][0]; - adH=bid.sizes[0][1]; - } - imp = - { - id: utils.getUniqueIdentifierStr(), - banner: { - w: adW, - h: adH - }, - tagid: tagId, - bidfloor: bidFloor - }; - sovrnImps.push(imp); - bidmanager.pbCallbackMap[imp.id] = bid; - }); - - // build bid request with impressions - var sovrnBidReq = { - id: utils.getUniqueIdentifierStr(), - imp: sovrnImps, - site:{ - domain: domain, - page: page - } - }; - - var scriptUrl = '//'+sovrnUrl+'?callback=window.pbjs.sovrnResponse' + - '&br=' + encodeURIComponent(JSON.stringify(sovrnBidReq)); - adloader.loadScript(scriptUrl, null); - } - - //expose the callback to the global object: - pbjs.sovrnResponse = function(sovrnResponseObj) { - var bid = {}; - // valid object? - if (sovrnResponseObj && sovrnResponseObj.id) { - // valid object w/ bid responses? - if (sovrnResponseObj.seatbid && sovrnResponseObj.seatbid.length !==0 && sovrnResponseObj.seatbid[0].bid && sovrnResponseObj.seatbid[0].bid.length !== 0) { - - sovrnResponseObj.seatbid[0].bid.forEach(function(sovrnBid){ - - var responseCPM; - var placementCode = ''; - var id = sovrnBid.impid; - - // try to fetch the bid request we sent Sovrn - var bidObj = bidmanager.getPlacementIdByCBIdentifer(id); - if (bidObj){ - placementCode = bidObj.placementCode; - bidObj.status = CONSTANTS.STATUS.GOOD; - - //place ad response on bidmanager._adResponsesByBidderId - responseCPM = parseFloat(sovrnBid.price); - - if(responseCPM !== 0) { - sovrnBid.placementCode = placementCode; - sovrnBid.size = bidObj.sizes; - var responseAd = sovrnBid.adm; - - // build impression url from response - var responseNurl = ''; - - //store bid response - //bid status is good (indicating 1) - bid = bidfactory.createBid(1); - bid.creative_id = sovrnBid.Id; - bid.bidderCode = 'sovrn'; - bid.cpm = responseCPM; - - //set ad content + impression url - // sovrn returns ' + - ''; - }, - /** - * Bid response builder. - * - * @param {Object} slotCriteria - Yieldbot bid criteria - * @private - */ - buildBid: function(slotCriteria) { - var bid = {}; - - if (slotCriteria && slotCriteria.ybot_ad && slotCriteria.ybot_ad !== 'n') { - - bid = bidfactory.createBid(ybotlib.BID_STATUS.AVAILABLE); - - bid.cpm = parseInt(slotCriteria.ybot_cpm) / 100.0 || 0; // Yieldbot CPM bids are in cents - - var szArr = slotCriteria.ybot_size ? slotCriteria.ybot_size.split('x') : [0,0], - slot = slotCriteria.ybot_slot || '', - sizeStr = slotCriteria.ybot_size || ''; // Creative template needs the dimensions string - - bid.width = szArr[0] || 0; - bid.height = szArr[1] || 0; - - bid.ad = ybotlib.buildCreative(slot, sizeStr); - - // Add Yieldbot parameters to allow publisher bidderSettings.yieldbot specific targeting - for (var k in slotCriteria) { - bid[k] = slotCriteria[k]; - } - - } else { - bid = bidfactory.createBid(ybotlib.BID_STATUS.EMPTY); - } - - bid.bidderCode = 'yieldbot'; - return bid; - }, - /** - * Yieldbot implementation of {@link module:adaptermanger.callBids} - * @param {Object} params - Adapter bid configuration object - * @private - */ - callBids: function(params) { - - var bids = params.bids || [], - ybotq = window.ybotq || []; - - ybotlib.pageLevelOption = false; - - ybotq.push(function () { - var yieldbot = window.yieldbot; - - utils._each(bids, function(v) { - var bid = v, - psn = bid.params && bid.params.psn || 'ERROR_DEFINE_YB_PSN', - slot = bid.params && bid.params.slot || 'ERROR_DEFINE_YB_SLOT'; - - yieldbot.pub(psn); - yieldbot.defineSlot(slot, {sizes: bid.sizes || []}); - - var cbId = utils.getUniqueIdentifierStr(); - bidmanager.pbCallbackMap[cbId] = bid; - ybotlib.definedSlots.push(cbId); - }); - - yieldbot.enableAsync(); - yieldbot.go(); - }); - - ybotq.push(function () { - ybotlib.handleUpdateState(); - }); - - adloader.loadScript('//cdn.yldbt.com/js/yieldbot.intent.js'); - }, - /** - * Yieldbot bid request callback handler. - * - * @see {@link YieldbotAdapter~_callBids} - * @private - */ - handleUpdateState: function() { - var yieldbot = window.yieldbot; - - utils._each(ybotlib.definedSlots, function(v) { - var slot, - criteria, - placementCode, - adapterConfig; - - adapterConfig = bidmanager.getPlacementIdByCBIdentifer(v) || {}; - slot = adapterConfig.params.slot || ''; - criteria = yieldbot.getSlotCriteria(slot); - - placementCode = adapterConfig.placementCode || 'ERROR_YB_NO_PLACEMENT'; - var bid = ybotlib.buildBid(criteria); - - bidmanager.addBidResponse(placementCode, bid); - - }); - } - } - return { - callBids: ybotlib.callBids - }; -}; - -module.exports = YieldbotAdapter; - -},{"../adloader":13,"../bidfactory":14,"../bidmanager":15,"../utils":20}],13:[function(require,module,exports){ -var utils = require('./utils'); -//add a script tag to the page, used to add /jpt call to page -exports.loadScript = function(tagSrc, callback) { - if(!tagSrc){ - utils.logError('Error attempting to request empty URL', 'adloader.js:loadScript'); - return; - } - var jptScript = document.createElement('script'); - jptScript.type = 'text/javascript'; - jptScript.async = true; - - - // Execute a callback if necessary - if (callback && typeof callback === "function") { - if (jptScript.readyState) { - jptScript.onreadystatechange = function() { - if (jptScript.readyState == "loaded" || jptScript.readyState == "complete") { - jptScript.onreadystatechange = null; - callback(); - } - }; - } else { - jptScript.onload = function() { - callback(); - }; - } - } - - //call function to build the JPT call - jptScript.src = tagSrc; - - //add the new script tag to the page - var elToAppend = document.getElementsByTagName('head'); - elToAppend = elToAppend.length ? elToAppend : document.getElementsByTagName('body'); - if (elToAppend.length) { - elToAppend = elToAppend[0]; - elToAppend.insertBefore(jptScript, elToAppend.firstChild); - } -}; - -exports.trackPixel = function(pixelUrl) { - //track a impbus tracking pixel - - //TODO: Decide of tracking via AJAX is sufficent, or do we need to - //run impression trackers via page pixels? - try { - - //add a cachebuster so we don't end up dropping any impressions - pixelUrl += '&rnd=' + Math.random(); - - if (pixelUrl) { - var img = document.createElement('img'); - img.src = pixelUrl; - } - - - } catch (e) { - - } -}; -},{"./utils":20}],14:[function(require,module,exports){ -var utils = require('./utils.js'); - -/** - Required paramaters - bidderCode, - height, - width, - statusCode - Optional paramaters - adId, - cpm, - ad, - adUrl, - dealId, - priceKeyString; - */ -function Bid(statusCode) { - var _bidId = utils.getUniqueIdentifierStr(), - _statusCode = statusCode || 0; - this.bidderCode = ''; - this.width = 0; - this.height = 0; - this.statusMessage = _getStatus(); - this.adId = _bidId; - - function _getStatus() { - switch (_statusCode) { - case 0: - return 'Pending'; - case 1: - return 'Bid available'; - case 2: - return 'Bid returned empty or error response'; - case 3: - return 'Bid timed out'; - } - } - this.getStatusCode = function() { - return _statusCode; - }; - - function _setStatusCode(status) { - this._statusCode = status; - //update status msg - this._statusMessage = this._getStatus(); - } - //returns the size of the bid creative. Concatenation of width and height by ‘x’. - this.getSize = function() { - return this.width + 'x' + this.height; - }; - -} - -// Bid factory function. -exports.createBid = function(statusCde) { - return new Bid(statusCde); -}; - -//module.exports = Bid; -},{"./utils.js":20}],15:[function(require,module,exports){ -var CONSTANTS = require('./constants.json'); -var utils = require('./utils.js'); -var adaptermanager = require('./adaptermanager'); -var events = require('./events'); - -var objectType_function = 'function'; -var objectType_undefined = 'undefined'; - -var externalCallbackByAdUnitArr = []; -var externalCallbackArr = []; -var externalOneTimeCallback = null; -var biddersByPlacementMap = {}; - -var pbCallbackMap = {}; -exports.pbCallbackMap = pbCallbackMap; - -var pbBidResponseByPlacement = {}; -exports.pbBidResponseByPlacement = pbBidResponseByPlacement; - -//this is used to look up the bid by bid ID later -var _adResponsesByBidderId = {}; -exports._adResponsesByBidderId = _adResponsesByBidderId; - -var bidResponseReceivedCount = {}; -exports.bidResponseReceivedCount = bidResponseReceivedCount; - -var expectedBidsCount = {}; - -var _allBidsAvailable = false; - -var _callbackExecuted = false; - -var defaultBidderSettingsMap = {}; -var bidderStartTimes = {}; - -exports.getPlacementIdByCBIdentifer = function(id) { - return pbCallbackMap[id]; -}; - - -exports.getBidResponseByAdUnit = function(adUnitCode) { - return pbBidResponseByPlacement; - -}; - - -exports.clearAllBidResponses = function(adUnitCode) { - _allBidsAvailable = false; - _callbackExecuted = false; - - //init bid response received count - initbidResponseReceivedCount(); - //init expected bids count - initExpectedBidsCount(); - //clear the callback handler flag - externalCallbackArr.called = false; - - for (var prop in this.pbBidResponseByPlacement) { - delete this.pbBidResponseByPlacement[prop]; - } -}; - -/** - * Returns a list of bidders that we haven't received a response yet - * @return {array} [description] - */ -exports.getTimedOutBidders = function(){ - var bidderArr = []; - utils._each(bidResponseReceivedCount,function(count,bidderCode){ - if(count === 0){ - bidderArr.push(bidderCode); - } - }); - - return bidderArr; -}; - -function initbidResponseReceivedCount(){ - - bidResponseReceivedCount = {}; - - for(var i=0; i 0){ - //else put into auction array if cpm > 0 - bidArrayTargeting.push({ - cpm: bid.cpm, - bid: bid - }); - } - //put all bids into bidArray by default - bidResponseArray.push(bidClone); - } - } - - // push the winning bid into targeting map - if (adUnitCode && bidArrayTargeting.length !== 0) { - var winningBid = getWinningBid(bidArrayTargeting); - var keyValues = winningBid.adserverTargeting; - pb_targetingMap[adUnitCode] = utils.extend(pb_targetingMap[adUnitCode], keyValues); - } - - return bidResponseArray; -} - -function getCloneBid(bid) { - var bidClone = {}; - //clone by json parse. This also gets rid of unwanted function properties - if (bid) { - var jsonBid = JSON.stringify(bid); - bidClone = JSON.parse(jsonBid); - - //clean up bid clone - delete bidClone.pbLg; - delete bidClone.pbMg; - delete bidClone.pbHg; - } - return bidClone; -} - -function resetBids() { - bidmanager.clearAllBidResponses(); - pb_bidderMap = {}; - pb_placements = []; - pb_targetingMap = {}; - pb_bidsTimedOut = false; -} - -function requestAllBids(tmout){ - var timeout = tmout; - resetBids(); - init(timeout); -} - - -////////////////////////////////// -// // -// Start Public APIs // -// // -////////////////////////////////// -/** - * This function returns the query string targeting parameters available at this moment for a given ad unit. Note that some bidder's response may not have been received if you call this function too quickly after the requests are sent. - * @param {string} [adunitCode] adUnitCode to get the bid responses for - * @alias module:pbjs.getAdserverTargetingForAdUnitCodeStr - * @return {array} returnObj return bids array - */ -pbjs.getAdserverTargetingForAdUnitCodeStr = function(adunitCode) { - // call to retrieve bids array - if(adunitCode){ - var res = pbjs.getAdserverTargetingForAdUnitCode(adunitCode); - return utils.transformAdServerTargetingObj(res); - } - else{ - utils.logMessage('Need to call getAdserverTargetingForAdUnitCodeStr with adunitCode'); - } -}; -/** - * This function returns the query string targeting parameters available at this moment for a given ad unit. Note that some bidder's response may not have been received if you call this function too quickly after the requests are sent. - * @param {string} [adunitCode] adUnitCode to get the bid responses for - * @alias module:pbjs.getAdserverTargetingForAdUnitCode - * @return {object} returnObj return bids - */ -pbjs.getAdserverTargetingForAdUnitCode = function(adunitCode) { - // call to populate pb_targetingMap - pbjs.getBidResponses(adunitCode); - - if (adunitCode) { - return pb_targetingMap[adunitCode]; - } - return pb_targetingMap; -}; -/** - * returns all ad server targeting for all ad units - * @return {object} Map of adUnitCodes and targeting values [] - * @alias module:pbjs.getAdserverTargeting - */ -pbjs.getAdserverTargeting = function() { - return pbjs.getAdserverTargetingForAdUnitCode(); -}; - -/** - * This function returns the bid responses at the given moment. - * @param {string} [adunitCode] adunitCode adUnitCode to get the bid responses for - * @alias module:pbjs.getBidResponses - * @return {object} map | object that contains the bidResponses - */ -pbjs.getBidResponses = function(adunitCode) { - var bidArrayTargeting = []; - var response = {}; - var bidArray = []; - var returnObj = {}; - - if (adunitCode) { - response = getBidResponsesByAdUnit(adunitCode); - bidArray = []; - if (response && response.bids) { - bidArray = buildBidResponse(response.bids); - } - - returnObj = { - bids: bidArray - }; - - } else { - response = getBidResponsesByAdUnit(); - for (var adUnit in response) { - if (response.hasOwnProperty(adUnit)) { - if (response && response[adUnit] && response[adUnit].bids) { - bidArray = buildBidResponse(response[adUnit].bids); - } - - returnObj[adUnit] = { - bids: bidArray - }; - - } - } - } - - return returnObj; - -}; -/** - * Returns bidResponses for the specified adUnitCode - * @param {String} adUnitCode adUnitCode - * @alias module:pbjs.getBidResponsesForAdUnitCode - * @return {Object} bidResponse object - */ -pbjs.getBidResponsesForAdUnitCode = function(adUnitCode) { - return pbjs.getBidResponses(adUnitCode); -}; -/** - * Set query string targeting on adUnits specified. The logic for deciding query strings is described in the section Configure AdServer Targeting. Note that this function has to be called after all ad units on page are defined. - * @param {array} [codeArr] an array of adUnitodes to set targeting for. - * @alias module:pbjs.setTargetingForAdUnitsGPTAsync - */ -pbjs.setTargetingForAdUnitsGPTAsync = function(codeArr) { - if (!window.googletag || !utils.isFn(window.googletag.pubads) || !utils.isFn(window.googletag.pubads().getSlots)) { - utils.logError('window.googletag is not defined on the page'); - return; - } - - //emit bid timeout event here - timeOutBidders(); - - var adUnitCodesArr = codeArr; - - if (typeof codeArr === objectType_string) { - adUnitCodesArr = [codeArr]; - } else if (typeof codeArr === objectType_object) { - adUnitCodesArr = codeArr; - } - - var placementBids = {}, - i = 0; - if (adUnitCodesArr) { - for (i = 0; i < adUnitCodesArr.length; i++) { - var code = adUnitCodesArr[i]; - //get all the slots from google tag - var slots = window.googletag.pubads().getSlots(); - for (var k = 0; k < slots.length; k++) { - - if (slots[k].getSlotElementId() === code || slots[k].getAdUnitPath() === code) { - placementBids = getBidResponsesByAdUnit(code); - setGPTAsyncTargeting(code, slots[k]); - } - } - } - } else { - //get all the slots from google tag - var slots = window.googletag.pubads().getSlots(); - for (i = 0; i < slots.length; i++) { - var adUnitCode = slots[i].getSlotElementId(); - if (adUnitCode) { - //placementBids = getBidsFromGTPIdentifier(slots[i]); - setGPTAsyncTargeting(adUnitCode, slots[i]); - } - } - } - -}; -/** - * Returns a string identifier (either DivId or adUnitPath) - * @param {[type]} slot [description] - * @return {[type]} [description] - */ -function getTargetingfromGPTIdentifier(slot){ - var targeting = null; - if(slot){ - //first get by elementId - targeting = pbjs.getAdserverTargetingForAdUnitCode(slot.getSlotElementId()); - //if not available, try by adUnitPath - if(!targeting){ - targeting = pbjs.getAdserverTargetingForAdUnitCode(slot.getAdUnitPath()); - } - } - return targeting; -} - -/** - - -/** - * Set query string targeting on all GPT ad units. - * @alias module:pbjs.setTargetingForGPTAsync - */ -pbjs.setTargetingForGPTAsync = function(codeArr) { - pbjs.setTargetingForAdUnitsGPTAsync(codeArr); -}; - -/** - * Returns a bool if all the bids have returned or timed out - * @alias module:pbjs.allBidsAvailable - * @return {bool} all bids available - */ -pbjs.allBidsAvailable = function() { - return bidmanager.allBidsBack(); -}; - -/** - * This function will render the ad (based on params) in the given iframe document passed through. Note that doc SHOULD NOT be the parent document page as we can't doc.write() asynchrounsly - * @param {object} doc document - * @param {string} id bid id to locate the ad - * @alias module:pbjs.renderAd - */ -pbjs.renderAd = function(doc, id) { - utils.logMessage('Calling renderAd with adId :' + id); - if (doc && id) { - try { - //lookup ad by ad Id - var adObject = bidmanager._adResponsesByBidderId[id]; - if (adObject) { - //emit 'bid won' event here - events.emit(BID_WON, adObject); - var height = adObject.height; - var width = adObject.width; - var url = adObject.adUrl; - var ad = adObject.ad; - - if (ad) { - doc.write(ad); - doc.close(); - if (doc.defaultView && doc.defaultView.frameElement) { - doc.defaultView.frameElement.width = width; - doc.defaultView.frameElement.height = height; - } - } - //doc.body.style.width = width; - //doc.body.style.height = height; - else if (url) { - doc.write(''); - doc.close(); - - if (doc.defaultView && doc.defaultView.frameElement) { - doc.defaultView.frameElement.width = width; - doc.defaultView.frameElement.height = height; - } - - } else { - utils.logError('Error trying to write ad. No ad for bid response id: ' + id); - } - - } else { - utils.logError('Error trying to write ad. Cannot find ad by given id : ' + id); - } - - } catch (e) { - utils.logError('Error trying to write ad Id :' + id + ' to the page:' + e.message); - } - } else { - utils.logError('Error trying to write ad Id :' + id + ' to the page. Missing document or adId'); - } - -}; - - -/* - * @deprecated - will be removed next release. Use pbjs.requestBids - */ -pbjs.requestBidsForAdUnit = function(adUnitCode) { - resetBids(); - init(adUnitCode); - -}; - -/** - * @deprecated - will be removed next release. Use pbjs.requestBids - */ -pbjs.requestBidsForAdUnits = function(adUnitsObj) { - if (!adUnitsObj || adUnitsObj.constructor !== Array) { - utils.logError('requestBidsForAdUnits must pass an array of adUnits'); - return; - } - resetBids(); - var adUnitBackup = pbjs.adUnits.slice(0); - pbjs.adUnits = adUnitsObj; - init(); - pbjs.adUnits = adUnitBackup; - -}; - -/** - * Remove adUnit from the pbjs configuration - * @param {String} adUnitCode the adUnitCode to remove - * @alias module:pbjs.removeAdUnit - */ -pbjs.removeAdUnit = function(adUnitCode) { - if (adUnitCode) { - for (var i = 0; i < pbjs.adUnits.length; i++) { - if (pbjs.adUnits[i].code === adUnitCode) { - pbjs.adUnits.splice(i, 1); - } - } - } -}; - - -/** - * Request bids ad-hoc. This function does not add or remove adUnits already configured. - * @param {Object} requestObj - * @param {string[]} requestObj.adUnitCodes adUnit codes to request. Use this or requestObj.adUnits - * @param {object[]} requestObj.adUnits AdUnitObjects to request. Use this or requestObj.adUnitCodes - * @param {number} [requestObj.timeout] Timeout for requesting the bids specified in milliseconds - * @param {function} [requestObj.bidsBackHandler] Callback to execute when all the bid responses are back or the timeout hits. - * @alias module:pbjs.requestBids - */ -pbjs.requestBids = function(requestObj) { - if (!requestObj) { - //utils.logMessage('requesting all bids'); - - requestAllBids(); - - } - else{ - var adUnitCodes = requestObj.adUnitCodes; - var adUnits = requestObj.adUnits; - var timeout = requestObj.timeout; - var bidsBackHandler = requestObj.bidsBackHandler; - var adUnitBackup = pbjs.adUnits.slice(0); - - if (typeof bidsBackHandler === objectType_function) { - bidmanager.addOneTimeCallback(bidsBackHandler); - } - - if (adUnitCodes && utils.isArray(adUnitCodes)) { - resetBids(); - init(timeout, adUnitCodes); - - } else if (adUnits && utils.isArray(adUnits)) { - resetBids(); - pbjs.adUnits = adUnits; - init(timeout); - } else { - //request all ads - requestAllBids(timeout); - } - - pbjs.adUnits = adUnitBackup; - } - -}; - -/** - * - * Add adunit(s) - * @param {(string|string[])} Array of adUnits or single adUnit Object. - * @alias module:pbjs.addAdUnits - */ -pbjs.addAdUnits = function(adUnitArr) { - if (utils.isArray(adUnitArr)) { - //append array to existing - pbjs.adUnits.push.apply(pbjs.adUnits, adUnitArr); - } else if (typeof adUnitArr === objectType_object) { - pbjs.adUnits.push(adUnitArr); - } -}; - - -/** - * Add a callback event - * @param {String} event event to attach callback to Options: "allRequestedBidsBack" | "adUnitBidsBack" - * @param {Function} func function to execute. Paramaters passed into the function: (bidResObj), [adUnitCode]); - * @alias module:pbjs.addCallback - * @returns {String} id for callback - */ -pbjs.addCallback = function(eventStr, func) { - var id = null; - if (!eventStr || !func || typeof func !== objectType_function) { - utils.logError('error registering callback. Check method signature'); - return id; - } - - id = utils.getUniqueIdentifierStr; - bidmanager.addCallback(id, func, eventStr); - return id; -}; - -/** - * Remove a callback event - * @param {string} cbId id of the callback to remove - * @alias module:pbjs.removeCallback - * @returns {String} id for callback - */ -pbjs.removeCallback = function(cbId) { - //todo -}; - -/** - * Wrapper to register bidderAdapter externally (adaptermanager.registerBidAdapter()) - * @param {[type]} bidderAdaptor [description] - * @param {[type]} bidderCode [description] - * @return {[type]} [description] - */ -pbjs.registerBidAdapter = function(bidderAdaptor, bidderCode){ - try{ - adaptermanager.registerBidAdapter(bidderAdaptor(), bidderCode); - } - catch(e){ - utils.logError('Error registering bidder adapter : ' + e.message); - } -}; - -/** - * - */ - pbjs.bidsAvailableForAdapter = function(bidderCode){ - - //TODO getAd - var bids = pb_bidderMap[bidderCode].bids; - - for (var i = 0; i < bids.length; i++) { - var adunitCode = bids[i].placementCode; - var responseObj = bidmanager.pbBidResponseByPlacement[adunitCode]; - - var bid = bidfactory.createBid(1); - // bid.creative_id = adId; - bid.bidderCode = bidderCode; - bid.adUnitCode = adunitCode; - bid.bidder = bidderCode; - // bid.cpm = responseCPM; - // bid.adUrl = jptResponseObj.result.ad; - // bid.width = jptResponseObj.result.width; - // bid.height = jptResponseObj.result.height; - // bid.dealId = jptResponseObj.result.deal_id; - - responseObj.bids.push(bid); - responseObj.bidsReceivedCount++; - bidmanager.pbBidResponseByPlacement[adunitCode] = responseObj; - }; - - bidmanager.increaseBidResponseReceivedCount(bidderCode); -} - -/** - * Wrapper to bidfactory.createBid() - * @param {[type]} statusCode [description] - * @return {[type]} [description] - */ -pbjs.createBid = function(statusCode){ - return bidfactory.createBid(statusCode); -}; - -/** - * Wrapper to bidmanager.addBidResponse - * @param {[type]} adUnitCode [description] - * @param {[type]} bid [description] - */ -pbjs.addBidResponse = function(adUnitCode, bid){ - bidmanager.addBidResponse(adUnitCode, bid); -}; - -/** - * Wrapper to adloader.loadScript - * @param {[type]} tagSrc [description] - * @param {Function} callback [description] - * @return {[type]} [description] - */ -pbjs.loadScript = function(tagSrc, callback){ - adloader.loadScript(tagSrc, callback); -}; - -/** - * This isn't ready yet - * return data for analytics - * @param {Function} [description] - * @return {[type]} [description] - -pbjs.getAnalyticsData = function(){ - var returnObj = {}; - var bidResponses = pbjs.getBidResponses(); - - //create return obj for all adUnits - for(var i=0;i https://developers.google.com/analytics/devguides/collection/ios/v3/limits-quotas?hl=en - _eventCount = 0, - //limit data sent by leaving this false - _enableDistribution = false, - _timedOutBidders = []; - - -/** - * This will enable sending data to google analytics. Only call once, or duplicate data will be sent! - * @param {object} gaOptions to set distribution and GA global (if renamed); - * @return {[type]} [description] - */ -exports.enableAnalytics = function(gaOptions) { - if(typeof gaOptions.global !== 'undefined'){ - _gaGlobal = gaOptions.global; - } - else{ - //default global is window.ga - _gaGlobal = 'ga'; - } - if(typeof gaOptions.enableDistribution !== 'undefined'){ - _enableDistribution = gaOptions.enableDistribution; - } - - var bid = null; - - //first send all events fired before enableAnalytics called - - var existingEvents = events.getEvents(); - utils._each(existingEvents, function(eventObj) { - var args = eventObj.args; - if (!eventObj) { - return; - } - if (eventObj.eventType === BID_REQUESTED) { - //bid is 1st args - bid = args[0]; - sendBidRequestToGa(bid); - } else if (eventObj.eventType === BID_RESPONSE) { - //bid is 2nd args - bid = args[1]; - sendBidResponseToGa(bid); - - } else if (eventObj.eventType === BID_TIMEOUT) { - var bidderArray = args[0]; - _timedOutBidders = bidderArray; - //todo disable event listeners - - } else if (eventObj.eventType === BID_WON) { - bid = args[0]; - sendBidWonToGa(bid); - } - }); - - //Next register event listeners to send data immediately - - //bidRequests - events.on(BID_REQUESTED, function(bidRequestObj) { - sendBidRequestToGa(bidRequestObj); - }); - - //bidResponses - events.on(BID_RESPONSE, function(adunit, bid) { - sendBidResponseToGa(bid); - sendBidTimeouts(bid); - }); - - //bidTimeouts - events.on(BID_TIMEOUT, function(bidderArray) { - _timedOutBidders = bidderArray; - }); - - //wins - events.on(BID_WON, function(bid) { - sendBidWonToGa(bid); - }); -}; - -/** - * Check if gaGlobal or window.ga is defined on page. If defined execute all the GA commands - */ -function checkAnalytics() { - if (_enableCheck && typeof window[_gaGlobal] === 'function' ) { - - for (var i = 0; i < _analyticsQueue.length; i++) { - _analyticsQueue[i].call(); - } - //override push to execute the command immediately from now on - _analyticsQueue.push = function(fn) { - fn.call(); - }; - //turn check into NOOP - _enableCheck = false; - } - utils.logMessage('event count sent to GA: ' + _eventCount); -} - - -function convertToCents(dollars) { - if (dollars) { - return Math.floor(dollars * 100); - } - return 0; -} - -function getLoadTimeDistribution(time) { - var distribution; - if (time >= 0 && time < 200) { - distribution = '0-200ms'; - } else if (time >= 200 && time < 300) { - distribution = '200-300ms'; - } else if (time >= 300 && time < 400) { - distribution = '300-400ms'; - } else if (time >= 400 && time < 500) { - distribution = '400-500ms'; - } else if (time >= 500 && time < 600) { - distribution = '500-600ms'; - } else if (time >= 600 && time < 800) { - distribution = '600-800ms'; - } else if (time >= 800 && time < 1000) { - distribution = '800-1000ms'; - } else if (time >= 1000 && time < 1200) { - distribution = '1000-1200ms'; - } else if (time >= 1200 && time < 1500) { - distribution = '1200-1500ms'; - } else if (time >= 1500 && time < 2000) { - distribution = '1500-2000ms'; - } else if (time >= 2000) { - distribution = '2000ms above'; - } - - return distribution; -} - - -function getCpmDistribution(cpm) { - var distribution; - if (cpm >= 0 && cpm < 0.5) { - distribution = '$0-0.5'; - } else if (cpm >= 0.5 && cpm < 1) { - distribution = '$0.5-1'; - } else if (cpm >= 1 && cpm < 1.5) { - distribution = '$1-1.5'; - } else if (cpm >= 1.5 && cpm < 2) { - distribution = '$1.5-2'; - } else if (cpm >= 2 && cpm < 2.5) { - distribution = '$2-2.5'; - } else if (cpm >= 2.5 && cpm < 3) { - distribution = '$2.5-3'; - } else if (cpm >= 3 && cpm < 4) { - distribution = '$3-4'; - } else if (cpm >= 4 && cpm < 6) { - distribution = '$4-6'; - } else if (cpm >= 6 && cpm < 8) { - distribution = '$6-8'; - } else if (cpm >= 8) { - distribution = '$8 above'; - } - return distribution; -} - - - -function sendBidRequestToGa(bid) { - if (bid && bid.bidderCode) { - _analyticsQueue.push(function() { - _eventCount++; - window[_gaGlobal]('send', 'event', _category, 'Requests', bid.bidderCode, 1, _disibleInteraction); - }); - } - //check the queue - checkAnalytics(); -} - - -function sendBidResponseToGa(bid) { - - if (bid && bid.bidderCode) { - _analyticsQueue.push(function() { - var cpmCents = convertToCents(bid.cpm), - bidder = bid.bidderCode; - if (typeof bid.timeToRespond !== 'undefined' && _enableDistribution) { - _eventCount++; - var dis = getLoadTimeDistribution(bid.timeToRespond); - window[_gaGlobal]('send', 'event', 'Prebid.js Load Time Distribution', dis, bidder, 1, _disibleInteraction); - } - if (bid.cpm > 0) { - _eventCount = _eventCount + 2; - var cpmDis = getCpmDistribution(bid.cpm); - if(_enableDistribution){ - _eventCount++; - window[_gaGlobal]('send', 'event', 'Prebid.js CPM Distribution', cpmDis, bidder, 1, _disibleInteraction); - } - window[_gaGlobal]('send', 'event', _category, 'Bids', bidder, cpmCents, _disibleInteraction); - window[_gaGlobal]('send', 'event', _category, 'Bid Load Time', bidder, bid.timeToRespond, _disibleInteraction); - } - }); - } - //check the queue - checkAnalytics(); -} - -function sendBidTimeouts(bid){ - - if(bid && bid.bidder){ - _analyticsQueue.push(function(){ - utils._each(_timedOutBidders, function(bidderCode){ - if(bid.bidder === bidderCode){ - _eventCount++; - window[_gaGlobal]('send', 'event', _category, 'Timeouts', bidderCode, bid.timeToRespond, _disibleInteraction); - } - }); - }); - } - checkAnalytics(); -} - -function sendBidWonToGa(bid) { - var cpmCents = convertToCents(bid.cpm); - _analyticsQueue.push(function() { - _eventCount++; - window[_gaGlobal]('send', 'event', _category, 'Wins', bid.bidderCode, cpmCents, _disibleInteraction); - }); - checkAnalytics(); -} - -},{"./constants.json":16,"./events":17,"./utils":20}],20:[function(require,module,exports){ -var CONSTANTS = require('./constants.json'); -var objectType_function = 'function'; -var objectType_undefined = 'undefined'; -var objectType_object = 'object'; -var objectType_string = 'string'; -var objectType_number = 'number'; - -var _loggingChecked = false; - -var _lgPriceCap = 5.00; -var _mgPriceCap = 20.00; -var _hgPriceCap = 20.00; - -var t_Arr = 'Array', - t_Str = 'String', - t_Fn = 'Function', - toString = Object.prototype.toString, - hasOwnProperty = Object.prototype.hasOwnProperty, - slice = Array.prototype.slice; - -/* - * Substitues into a string from a given map using the token - * Usage - * var str = 'text %%REPLACE%% this text with %%SOMETHING%%'; - * var map = {}; - * map['replace'] = 'it was subbed'; - * map['something'] = 'something else'; - * console.log(replaceTokenInString(str, map, '%%')); => "text it was subbed this text with something else" - */ -exports.replaceTokenInString = function(str, map, token) { - this._each(map, function (value, key) { - value = (value === undefined) ? '' : value; - - var keyString = token + key.toUpperCase() + token, - re = new RegExp(keyString, 'g'); - - str = str.replace(re, value); - }); - return str; -}; - -/* utility method to get incremental integer starting from 1 */ -var getIncrementalInteger = (function() { - var count = 0; - return function() { - count++; - return count; - }; -})(); - -function _getUniqueIdentifierStr() { - return getIncrementalInteger() + Math.random().toString(16).substr(2); -} - -//generate a random string (to be used as a dynamic JSONP callback) -exports.getUniqueIdentifierStr = _getUniqueIdentifierStr; - -exports.getBidIdParamater = function(key, paramsObj) { - if (paramsObj && paramsObj[key]) { - return paramsObj[key]; - } - return ''; -}; - -exports.tryAppendQueryString = function(existingUrl, key, value) { - if (value) { - return existingUrl += key + '=' + encodeURIComponent(value) + '&'; - } - return existingUrl; -}; - - -//parse a query string object passed in bid params -//bid params should be an object such as {key: "value", key1 : "value1"} -exports.parseQueryStringParameters = function(queryObj) { - var result = ""; - for (var k in queryObj){ - if (queryObj.hasOwnProperty(k)) - result += k + "=" + encodeURIComponent(queryObj[k]) + "&"; - } - return result; -}; - - -//transform an AdServer targeting bids into a query string to send to the adserver -//bid params should be an object such as {key: "value", key1 : "value1"} -exports.transformAdServerTargetingObj = function(adServerTargeting) { - var result = ""; - if (!adServerTargeting) - return ""; - for (var k in adServerTargeting) - if (adServerTargeting.hasOwnProperty(k)) - result += k + "=" + encodeURIComponent(adServerTargeting[k]) + "&"; - return result; -}; - -//Copy all of the properties in the source objects over to the target object -//return the target object. -exports.extend = function(target, source){ - target = target || {}; - - this._each(source,function(value,prop){ - if (typeof source[prop] === objectType_object) { - target[prop] = this.extend(target[prop], source[prop]); - } else { - target[prop] = source[prop]; - } - }); - return target; -}; - -//parse a GPT-Style General Size Array or a string like "300x250" into a format -//suitable for passing to a GPT tag, may include size and/or promo sizes -exports.parseSizesInput = function(sizeObj) { - var sizeQueryString; - var parsedSizes = []; - - //if a string for now we can assume it is a single size, like "300x250" - if (typeof sizeObj === objectType_string) { - //multiple sizes will be comma-separated - var sizes = sizeObj.split(','); - //regular expression to match strigns like 300x250 - //start of line, at least 1 number, an "x" , then at least 1 number, and the then end of the line - var sizeRegex = /^(\d)+x(\d)+$/i; - if (sizes) { - for (var curSizePos in sizes) { - if (hasOwn(sizes, curSizePos) && sizes[curSizePos].match(sizeRegex)) { - parsedSizes.push(sizes[curSizePos]); - } - } - } - } else if (typeof sizeObj === objectType_object) { - var sizeArrayLength = sizeObj.length; - //don't process empty array - if (sizeArrayLength > 0) { - //if we are a 2 item array of 2 numbers, we must be a SingleSize array - if (sizeArrayLength === 2 && typeof sizeObj[0] === objectType_number && typeof sizeObj[1] === objectType_number) { - parsedSizes.push(this.parseGPTSingleSizeArray(sizeObj)); - } else { - //otherwise, we must be a MultiSize array - for (var i = 0; i < sizeArrayLength; i++) { - parsedSizes.push(this.parseGPTSingleSizeArray(sizeObj[i])); - } - - } - } - } - - - //combine string into proper querystring for impbus - var 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 (var 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); - } - } - } - - return sizeQueryString; - -}; - -//parse a GPT style sigle size array, (i.e [300,250]) -//into an AppNexus style string, (i.e. 300x250) -exports.parseGPTSingleSizeArray = function(singleSize) { - //if we aren't exactly 2 items in this array, it is invalid - if (this.isArray(singleSize) && singleSize.length === 2 && (!isNaN(singleSize[0]) && !isNaN(singleSize[1]))) { - return singleSize[0] + 'x' + singleSize[1]; - } -}; - -exports.getTopWindowUrl = function() { - try { - return window.top.location.href; - } catch (e) { - return window.location.href; - } -}; - -exports.logMessage = function(msg) { - if (debugTurnedOn() && hasConsoleLogger()) { - console.log('MESSAGE: ' + msg); - } -}; - -function hasConsoleLogger() { - return (window.console && window.console.log); -} -exports.hasConsoleLogger = hasConsoleLogger; - -var errLogFn = (function (hasLogger) { - if (!hasLogger) return ''; - return window.console.error ? 'error' : 'log'; -}(hasConsoleLogger())); - -var debugTurnedOn = function() { - if (pbjs.logging === false && _loggingChecked === false) { - pbjs.logging = getParameterByName(CONSTANTS.DEBUG_MODE).toUpperCase() === 'TRUE'; - _loggingChecked = true; - } - - if (pbjs.logging) { - return true; - } - return false; - -}; -exports.debugTurnedOn = debugTurnedOn; - -exports.logError = function(msg, code, exception) { - var errCode = code || 'ERROR'; - if (debugTurnedOn() && hasConsoleLogger()) { - console[errLogFn].call(console, errCode + ': ' + msg, exception || ''); - } -}; - -exports.createInvisibleIframe = function _createInvisibleIframe() { - var f = document.createElement('iframe'); - f.id = _getUniqueIdentifierStr(); - f.height = 0; - f.width = 0; - f.border = '0px'; - f.hspace = '0'; - f.vspace = '0'; - f.marginWidth = '0'; - f.marginHeight = '0'; - f.style.border = '0'; - f.scrolling = 'no'; - f.frameBorder = '0'; - f.src = 'about:self'; - f.style = 'display:none'; - return f; -}; - -/* - * Check if a given paramater name exists in query string - * and if it does return the value - */ -var getParameterByName = function(name) { - var regexS = '[\\?&]' + name + '=([^&#]*)', - regex = new RegExp(regexS), - results = regex.exec(window.location.search); - if (results === null) { - return ''; - } - return decodeURIComponent(results[1].replace(/\+/g, ' ')); -}; - -exports.getPriceBucketString = function(cpm) { - var low = '', - med = '', - high = '', - cpmFloat = 0, - returnObj = { - low: low, - med: med, - high: high - }; - try { - cpmFloat = parseFloat(cpm); - if (cpmFloat) { - //round to closet .5 - if (cpmFloat > _lgPriceCap) { - returnObj.low = _lgPriceCap.toFixed(2); - } else { - returnObj.low = (Math.floor(cpm * 2) / 2).toFixed(2); - } - - //round to closet .1 - if (cpmFloat > _mgPriceCap) { - returnObj.med = _mgPriceCap.toFixed(2); - } else { - returnObj.med = (Math.floor(cpm * 10) / 10).toFixed(2); - } - - //round to closet .01 - if (cpmFloat > _hgPriceCap) { - returnObj.high = _hgPriceCap.toFixed(2); - } else { - returnObj.high = (Math.floor(cpm * 100) / 100).toFixed(2); - } - } - } catch (e) { - this.logError('Exception parsing CPM :' + e.message); - } - return returnObj; - -}; - -/** - * This function validates paramaters. - * @param {object[string]} paramObj [description] - * @param {string[]} requiredParamsArr [description] - * @return {bool} Bool if paramaters are valid - */ -exports.hasValidBidRequest = function(paramObj, requiredParamsArr, adapter){ - - for(var i = 0; i < requiredParamsArr.length; i++){ - var found = false; - - this._each(paramObj, function (value, key) { - if (key === requiredParamsArr[i]) { - found = true; - } - }); - - if(!found){ - this.logError('Params are missing for bid request. One of these required paramaters are missing: ' + requiredParamsArr, adapter); - return false; - } - } - - return true; -}; - -// Handle addEventListener gracefully in older browsers -exports.addEventHandler = function(element, event, func) { - if (element.addEventListener) { - element.addEventListener(event, func, true); - } else if (element.attachEvent) { - element.attachEvent('on' + event, func); - } - }; - /** - * Return if the object is of the - * given type. - * @param {*} object to test - * @param {String} _t type string (e.g., Array) - * @return {Boolean} if object is of type _t - */ -exports.isA = function(object, _t) { - return toString.call(object) === '[object ' + _t + ']'; -}; - -exports.isFn = function (object) { - return this.isA(object, t_Fn); -}; - -exports.isStr = function (object) { - return this.isA(object, t_Str); -}; - -exports.isArray = function (object) { - return this.isA(object, t_Arr); -}; - -/** - * Return if the object is "empty"; - * this includes falsey, no keys, or no items at indices - * @param {*} object object to test - * @return {Boolean} if object is empty - */ -exports.isEmpty = function(object) { - if (!object) return true; - if (this.isArray(object) || this.isStr(object)) return !(object.length > 0); - for (var k in object) { - if (hasOwnProperty.call(object, k)) return false; - } - return true; - }; - - /** - * Iterate object with the function - * falls back to es5 `forEach` - * @param {Array|Object} object - * @param {Function(value, key, object)} fn - */ -exports._each = function(object, fn) { - if (this.isEmpty(object)) return; - if (this.isFn(object.forEach)) return object.forEach(fn, this); - - var k = 0, - l = object.length; - - if (l > 0) { - for (; k < l; k++) fn(object[k], k, object); - } else { - for (k in object) { - if (hasOwnProperty.call(object, k)) fn.call(this, object[k], k); - } - } - }; - -exports.contains = function(a, obj) { - if(this.isEmpty(a)){ - return false; - } - if (this.isFn(a.indexOf)) { - return a.indexOf(obj) !== -1; - } - var i = a.length; - while (i--) { - if (a[i] === obj) { - return true; - } - } - return false; -}; - -/** - * Map an array or object into another array - * given a function - * @param {Array|Object} object - * @param {Function(value, key, object)} callback - * @return {Array} - */ -exports._map = function (object, callback) { - if (this.isEmpty(object)) return []; - if (this.isFn(object.map)) return object.map(callback); - var output = []; - this._each(object, function (value, key) { - output.push(callback(value, key, object)); - }); - return output; -}; - -var hasOwn = function(objectToCheck, propertyToCheckFor) { - if (objectToCheck.hasOwnProperty) { - return objectToCheck.hasOwnProperty(propertyToCheckFor); - } else { - return (typeof objectToCheck[propertyToCheckFor] !== UNDEFINED) && (objectToCheck.constructor.prototype[propertyToCheckFor] !== objectToCheck[propertyToCheckFor]); - } -}; -},{"./constants.json":16}]},{},[18]) \ No newline at end of file diff --git a/dist/prebid.min.js b/dist/prebid.min.js deleted file mode 100644 index c313f8de088..00000000000 --- a/dist/prebid.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/* Prebid.js v0.5.0 -Updated : 2016-01-11 */ -!function e(t,r,i){function n(s,d){if(!r[s]){if(!t[s]){var o="function"==typeof require&&require;if(!d&&o)return o(s,!0);if(a)return a(s,!0);throw new Error("Cannot find module '"+s+"'")}var c=r[s]={exports:{}};t[s][0].call(c.exports,function(e){var r=t[s][1][e];return n(r?r:e)},c,c.exports,e,t,r,i)}return r[s].exports}for(var a="function"==typeof require&&require,s=0;s=i){s=!0,d=p[o],a.trackPixel(d);break}s||(d=l,a.trackPixel(d)),n.logMessage("latency for placement code : "+r+" : "+i+" ms. Tracking URL Fired : "+d)}}function c(e,t){var r=n.getBidIdParamater("placementId",e.params),i=n.getBidIdParamater("memberId",e.params),a=n.getBidIdParamater("invCode",e.params),s=n.getBidIdParamater("query",e.params),d=n.getBidIdParamater("referrer",e.params),o=n.getBidIdParamater("alt_referrer",e.params),c="http"+("https:"===document.location.protocol?"s://secure.adnxs.com/jpt?":"://ib.adnxs.com/jpt?");c=n.tryAppendQueryString(c,"callback","pbjs.handleAnCB"),c=n.tryAppendQueryString(c,"callback_uid",t),c=n.tryAppendQueryString(c,"psa","0"),c=n.tryAppendQueryString(c,"id",r),c=n.tryAppendQueryString(c,"member_id",i),c=n.tryAppendQueryString(c,"code",a);var u=n.parseSizesInput(e.sizes);u&&(c+=u+"&");var p=n.parseQueryStringParameters(s);p&&(c+=p);var l=n.extend({},e.params);delete l.placementId,delete l.memberId,delete l.invCode,delete l.query,delete l.referrer,delete l.alt_referrer;var f=n.parseQueryStringParameters(l);return f&&(c+=f),""===d&&(d=n.getTopWindowUrl()),c=n.tryAppendQueryString(c,"referrer",d),c=n.tryAppendQueryString(c,"alt_referrer",o),c.lastIndexOf("&")===c.length-1&&(c=c.substring(0,c.length-1)),e.startTime=(new Date).getTime(),c}var u=o.createNew("appnexus"),p=[];p[100]=e(21139),p[200]=e(21140),p[300]=e(21141),p[400]=e(21142),p[500]=e(21143),p[600]=e(21144),p[700]=e(21145),p[800]=e(21146),p[1e3]=e(21147),p[1300]=e(21148),p[1600]=e(21149),p[2e3]=e(21150),p[5e3]=e(21151),p[1e4]=e(21152);{var l=e(21154);e(21153)}return u.callBids=function(e){var t=u.getBidderCode(),r=e.bids,i=r.length;s.setExpectedBidsCount(t,i);for(var d=0;i>d;d++){var o=r[d],p=n.getUniqueIdentifierStr();a.loadScript(c(o,p)),s.pbCallbackMap[p]=o}},pbjs.handleAnCB=function(e){var r;if(e&&e.callback_uid){var n,a=e.callback_uid,o="",c=s.getPlacementIdByCBIdentifer(a);if(c){r=c.bidder,o=c.placementCode,c.status=i.STATUS.GOOD;try{t(c.startTime,(new Date).getTime(),o)}catch(u){}}var p=[];if(e.result&&e.result.cpm&&0!==e.result.cpm){n=parseInt(e.result.cpm,10),n/=1e4;var l=(e.result.ad,e.result.creative_id);p=d.createBid(1),p.creative_id=l,p.bidderCode=r,p.cpm=n,p.adUrl=e.result.ad,p.width=e.result.width,p.height=e.result.height,p.dealId=e.result.deal_id,s.addBidResponse(o,p)}else p=d.createBid(2),p.bidderCode=r,s.addBidResponse(o,p)}},{callBids:u.callBids,setBidderCode:u.setBidderCode,createNew:r.createNew,buildJPTCall:c}};r.createNew=function(){return new c}},{"../adloader.js":13,"../bidfactory.js":14,"../bidmanager.js":15,"../constants.json":16,"../utils.js":20,"./adapter.js":2}],5:[function(e,t,r){var i=(e("../constants.json"),e("../utils.js"),e("../bidfactory.js")),n=e("../bidmanager.js"),a=e("../adloader"),s=function(){function e(e){s=e.bids||[],t(s).forEach(r)}function t(e){var t,r={},i=[];e.forEach(function(e){r[e.params.nid]=e});for(t in r)r.hasOwnProperty(t)&&i.push(r[t]);return i}function r(e){var t=e.params.varname,r="//rtax.criteo.com/delivery/rta/rta.js?netId="+encodeURI(e.params.nid)+"&cookieName="+encodeURI(e.params.cookiename)+"&rnd="+Math.floor(99999999999*Math.random())+"&varName="+encodeURI(t);a.loadScript(r,function(r){var a,d=window[t];s.forEach(function(t){t.params.nid===e.params.nid&&(d?(a=i.createBid(1),a.bidderCode="criteo",a.keys=d.split(";")):(a=i.createBid(2),a.bidderCode="criteo"),n.addBidResponse(t.placementCode,a))})})}var s;return{callBids:e}};t.exports=s},{"../adloader":13,"../bidfactory.js":14,"../bidmanager.js":15,"../constants.json":16,"../utils.js":20}],6:[function(e,t,r){var n=(e("../constants.json"),e("../utils.js")),a=e("../bidfactory.js"),s=e("../bidmanager.js"),d=e("../adloader.js"),o="INDEXEXCHANGE",c="indexExchange",u=!0,p=function(){};window.cygnus_index_args={};var l=[[728,90],[120,600],[300,250],[160,600],[336,280],[234,60],[300,600],[300,50],[320,50],[970,250],[300,1050],[970,90],[180,150]],f=function(){function e(e){var t=n[e];return"string"==typeof t?t:"\\u"+("0000"+e.charCodeAt(0).toString(16)).slice(-4)}function t(t){return i.lastIndex=0,i.test(t)?t.replace(i,e):t}function r(e,t,r){if(this.initialized=!1,"number"!=typeof e||e%1!==0||0>e)throw"Invalid Site ID";if("number"==typeof r&&r%1===0&&r>=0&&(this.timeoutDelay=r),this.siteID=e,this.impressions=[],this._parseFnName=void 0,top===self?(this.sitePage=location.href,this.topframe=1):(this.sitePage=document.referrer,this.topframe=0),"undefined"!=typeof t){if("function"!=typeof t)throw"Invalid jsonp target function";this._parseFnName="cygnus_index_args.parseFn"}_IndexRequestData.requestCounter="undefined"==typeof _IndexRequestData.requestCounter?Math.floor(256*Math.random()):(_IndexRequestData.requestCounter+1)%256,this.requestID=String((new Date).getTime()%2592e3*256+_IndexRequestData.requestCounter+256),this.initialized=!0}cygnus_index_args.parseFn=p;var i=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,n={"\b":"\\b"," ":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"};r.prototype.serialize=function(){var e='{"id":'+this.requestID+',"site":{"page":"'+t(this.sitePage)+'"';"string"==typeof document.referrer&&(e+=',"ref":"'+t(document.referrer)+'"'),e+='},"imp":[';for(var r=0;r0&&(e+=',"ext": {'+n.join()+"}"),e+=r+1==this.impressions.length?"}":"},"}return e+="]}"},r.prototype.setPageOverride=function(e){return"string"!=typeof e||e.match(/^\s*$/)?!1:(this.sitePage=e,!0)},r.prototype.addImpression=function(e,t,r,i,n,a){var s={id:String(this.impressions.length+1)};if("number"!=typeof e||1>=e)return null;if("number"!=typeof t||1>=t)return null;if(("string"==typeof n||"number"==typeof n)&&String(n).length<=50&&(s.slotID=String(n)),s.w=e,s.h=t,void 0!==r&&"number"!=typeof r)return null;if("number"==typeof r){if(0>r)return null;if(s.bidfloor=r,void 0!==i&&"string"!=typeof i)return null;s.bidfloorcur=i}if("undefined"!=typeof a){if(!("number"==typeof a&&a%1===0&&a>=0))return null;s.siteID=a}return this.impressions.push(s),s.id},r.prototype.buildRequest=function(){if(0!==this.impressions.length&&this.initialized===!0){var e=encodeURIComponent(this.serialize()),t="https:"===window.location.protocol?"https://as-sec.casalemedia.com":"http://as.casalemedia.com";return t+="/headertag?v=9&x3=1&fn=cygnus_index_parse_res&s="+this.siteID+"&r="+e,"number"==typeof this.timeoutDelay&&this.timeoutDelay%1===0&&this.timeoutDelay>=0&&(t+="&t="+this.timeoutDelay),t}};try{if("undefined"==typeof cygnus_index_args||"undefined"==typeof cygnus_index_args.siteID||"undefined"==typeof cygnus_index_args.slots)return;"undefined"==typeof _IndexRequestData&&(_IndexRequestData={},_IndexRequestData.impIDToSlotID={},_IndexRequestData.reqOptions={});var a=new r(cygnus_index_args.siteID,cygnus_index_args.parseFn,cygnus_index_args.timeout);cygnus_index_args.url&&"string"==typeof cygnus_index_args.url&&a.setPageOverride(cygnus_index_args.url),_IndexRequestData.impIDToSlotID[a.requestID]={},_IndexRequestData.reqOptions[a.requestID]={};for(var s,d,o=0;o',e+="",e+="",e=i.replaceTokenInString(e,t,"%%")}var s,d,o=[];return pbjs.handlePubmaticCallback=function(e){var t,r,i,d,o,c=e&&e.bidDetailsMap||{},u=e&&e.progKeyValueMap||{};for(t=0;t",a+='',a+="",a=n.replaceTokenInString(a,s,"%%")}var o={};return window.pbjs=window.pbjs||{que:[]},window.pbjs.handleRubiconCallback=function(e){var r="",d={};if(e&&"ok"===e.status)try{var c="",u=s.getPlacementIdByCBIdentifer(t(e));if(u&&(r=u.placementCode,u.status=i.STATUS.GOOD,c=u.iframeId),e.ads&&e.ads[0]&&"ok"===e.ads[0].status){d=a.createBid(1);var p,l=e.ads[0],f=o[l.size_id],g=0,m=0,b=window.frames[c];if(p=b.contentWindow?b.contentWindow.RubiconAdServing:b.window.RubiconAdServing,p&&p.AdSizes){f=p.AdSizes[l.size_id];var h=f.dim.split("x");g=h[0],m=h[1]}d.cpm=l.cpm,d.ad="",d.ad_id=l.ad_id,d.bidderCode="rubicon",d.sizeId=l.size_id,d.width=g,d.height=m}else{d=a.createBid(2),d.bidderCode="rubicon";var u=s.getPlacementIdByCBIdentifer(t(e));u&&(r=u.placementCode)}}catch(v){n.logError("Error parsing rubicon response bid: "+v.message)}else{d=a.createBid(2),d.bidderCode="rubicon";var u=s.getPlacementIdByCBIdentifer(t(e));u&&(r=u.placementCode)}s.addBidResponse(r,d)},{callBids:e}};t.exports=d},{"../bidfactory.js":14,"../bidmanager.js":15,"../constants.json":16,"../utils.js":20}],11:[function(e,t,r){var i=e("../constants.json"),n=e("../utils.js"),a=e("../bidfactory.js"),s=e("../bidmanager.js"),d=e("../adloader"),o="",c=function(){function e(e){var i=e.bids||[];r(t(i))}function t(e){var t,r={},i=[];e.forEach(function(e){r[n.getBidIdParamater("tagid",e.params)]=e});for(t in r)r.hasOwnProperty(t)&&i.push(r[t]);return i}function r(e){var t=window.location.host,r=window.location.pathname+location.search+location.hash,i=[];o=e[0].placementCode,n._each(e,function(e){var t=n.getBidIdParamater("tagid",e.params),r=n.getBidIdParamater("bidfloor",e.params),a=0,d=0,o=e.sizes.length;2===o&&"number"==typeof e.sizes[0]&&"number"==typeof e.sizes[1]?(a=e.sizes[0],d=e.sizes[1]):(a=e.sizes[0][0],d=e.sizes[0][1]),imp={id:n.getUniqueIdentifierStr(),banner:{w:a,h:d},tagid:t,bidfloor:r},i.push(imp),s.pbCallbackMap[imp.id]=e});var a={id:n.getUniqueIdentifierStr(),imp:i,site:{domain:t,page:r}},u="//"+c+"?callback=window.pbjs.sovrnResponse&br="+encodeURIComponent(JSON.stringify(a));d.loadScript(u,null)}var c="ap.lijit.com/rtb/bid";return pbjs.sovrnResponse=function(e){var t={};e&&e.id&&e.seatbid&&0!==e.seatbid.length&&e.seatbid[0].bid&&0!==e.seatbid[0].bid.length?e.seatbid[0].bid.forEach(function(e){var r,n="",d=e.impid,o=s.getPlacementIdByCBIdentifer(d);if(o)if(n=o.placementCode,o.status=i.STATUS.GOOD,r=parseFloat(e.price),0!==r){e.placementCode=n,e.size=o.sizes;var c=e.adm,u='';t=a.createBid(1),t.creative_id=e.Id,t.bidderCode="sovrn",t.cpm=r,t.ad=decodeURIComponent(c+u);var p=o.sizes.length;2===p&&"number"==typeof o.sizes[0]&&"number"==typeof o.sizes[1]?(t.width=o.sizes[0],t.height=o.sizes[1]):(t.width=o.sizes[0][0],t.height=o.sizes[0][1]),s.addBidResponse(n,t)}else t=a.createBid(2),t.bidderCode="sovrn",s.addBidResponse(n,t);else t=a.createBid(2),t.bidderCode="sovrn",s.addBidResponse(n,t)}):(t=a.createBid(2),t.bidderCode="sovrn",s.addBidResponse(o,t))},{callBids:e}};t.exports=c},{"../adloader":13,"../bidfactory.js":14,"../bidmanager.js":15,"../constants.json":16,"../utils.js":20}],12:[function(e,t,r){var i=e("../adloader"),n=e("../bidfactory"),a=e("../bidmanager"),s=e("../utils"),d=function(){window.ybotq=window.ybotq||[];var e={BID_STATUS:{PENDING:0,AVAILABLE:1,EMPTY:2},definedSlots:[],pageLevelOption:!1,buildCreative:function(e,t){return'"},buildBid:function(t){var r={};if(t&&t.ybot_ad&&"n"!==t.ybot_ad){r=n.createBid(e.BID_STATUS.AVAILABLE),r.cpm=parseInt(t.ybot_cpm)/100||0;var i=t.ybot_size?t.ybot_size.split("x"):[0,0],a=t.ybot_slot||"",s=t.ybot_size||"";r.width=i[0]||0,r.height=i[1]||0,r.ad=e.buildCreative(a,s);for(var d in t)r[d]=t[d]}else r=n.createBid(e.BID_STATUS.EMPTY);return r.bidderCode="yieldbot",r},callBids:function(t){var r=t.bids||[],n=window.ybotq||[];e.pageLevelOption=!1,n.push(function(){var t=window.yieldbot;s._each(r,function(r){var i=r,n=i.params&&i.params.psn||"ERROR_DEFINE_YB_PSN",d=i.params&&i.params.slot||"ERROR_DEFINE_YB_SLOT";t.pub(n),t.defineSlot(d,{sizes:i.sizes||[]});var o=s.getUniqueIdentifierStr();a.pbCallbackMap[o]=i,e.definedSlots.push(o)}),t.enableAsync(),t.go()}),n.push(function(){e.handleUpdateState()}),i.loadScript("//cdn.yldbt.com/js/yieldbot.intent.js")},handleUpdateState:function(){var t=window.yieldbot;s._each(e.definedSlots,function(r){var i,n,s,d;d=a.getPlacementIdByCBIdentifer(r)||{},i=d.params.slot||"",n=t.getSlotCriteria(i),s=d.placementCode||"ERROR_YB_NO_PLACEMENT";var o=e.buildBid(n);a.addBidResponse(s,o)})}};return{callBids:e.callBids}};t.exports=d},{"../adloader":13,"../bidfactory":14,"../bidmanager":15,"../utils":20}],13:[function(e,t,r){var i=e("./utils");r.loadScript=function(e,t){if(!e)return void i.logError("Error attempting to request empty URL","adloader.js:loadScript");var r=document.createElement("script");r.type="text/javascript",r.async=!0,t&&"function"==typeof t&&(r.readyState?r.onreadystatechange=function(){("loaded"==r.readyState||"complete"==r.readyState)&&(r.onreadystatechange=null,t())}:r.onload=function(){t()}),r.src=e;var n=document.getElementsByTagName("head");n=n.length?n:document.getElementsByTagName("body"),n.length&&(n=n[0],n.insertBefore(r,n.firstChild))},r.trackPixel=function(e){try{if(e+="&rnd="+Math.random()){var t=document.createElement("img");t.src=e}}catch(r){}}},{"./utils":20}],14:[function(e,t,r){function i(e){function t(){switch(i){case 0:return"Pending";case 1:return"Bid available";case 2:return"Bid returned empty or error response";case 3:return"Bid timed out"}}var r=n.getUniqueIdentifierStr(),i=e||0;this.bidderCode="",this.width=0,this.height=0,this.statusMessage=t(),this.adId=r,this.getStatusCode=function(){return i},this.getSize=function(){return this.width+"x"+this.height}}var n=e("./utils.js");r.createBid=function(e){return new i(e)}},{"./utils.js":20}],15:[function(e,t,r){function i(){T={};for(var e=0;et)&&(e=!1)}),e}function f(e){var t=e.bidderCode,r=e.cpm;if(t&&pbjs.bidderSettings&&pbjs.bidderSettings[t]&&typeof pbjs.bidderSettings[t].bidCpmAdjustment===h)try{r=pbjs.bidderSettings[t].bidCpmAdjustment.call(null,e.cpm)}catch(i){m.logError("Error during bid adjustment","bidmanager.js",i)}0!==r&&(e.cpm=r)}var g=e("./constants.json"),m=e("./utils.js"),b=(e("./adaptermanager"),e("./events")),h="function",v="undefined",y=[],_=[],B=null,I={},w={};r.pbCallbackMap=w;var E={};r.pbBidResponseByPlacement=E;var C={};r._adResponsesByBidderId=C;var T={};r.bidResponseReceivedCount=T;var S={},R=!1,A=!1,j={},D={};r.getPlacementIdByCBIdentifer=function(e){return w[e]},r.getBidResponseByAdUnit=function(e){return E},r.clearAllBidResponses=function(e){R=!1,A=!1,i(),a(),_.called=!1;for(var t in this.pbBidResponseByPlacement)delete this.pbBidResponseByPlacement[t]},r.getTimedOutBidders=function(){var e=[];return m._each(T,function(t,r){0===t&&e.push(r)}),e},r.increaseBidResponseReceivedCount=function(e){n(e)},r.setExpectedBidsCount=function(e,t){S[e]=t},r.getExpectedBidsCount=s,r.addBidResponse=function(e,t){var r={};if(t){t.requestTimestamp=D[t.bidderCode],t.responseTimestamp=(new Date).getTime(),t.timeToRespond=t.responseTimestamp-t.requestTimestamp,n(t.bidderCode),2===t.getStatusCode()&&(t.cpm=0),b.emit(g.EVENTS.BID_ADJUSTMENT,t),b.emit(g.EVENTS.BID_RESPONSE,e,t);var i=m.getPriceBucketString(t.cpm,t.height,t.width);t.pbLg=i.low,t.pbMg=i.med,t.pbHg=i.high,t.adUnitCode=e,t.bidder=t.bidderCode;var a={};t.bidderCode&&0!==t.cpm&&(a=this.getKeyValueTargetingPairs(t.bidderCode,t),t.adserverTargeting=a),t.adId&&(C[t.adId]=t),e&&E[e]?(r=E[e],r.bids.push(t),r.bidsReceivedCount++):m.logError("Internal error in bidmanager.addBidResponse. Params: "+e+" & "+t)}else r=this.createEmptyBidResponseObj();E[e]=r,this.checkIfAllBidsAreIn(e)},r.createEmptyBidResponseObj=function(){return{bids:[],allBidsAvailable:!1,bidsReceivedCount:0}},r.getKeyValueTargetingPairs=function(e,t){var r={},i=pbjs.bidderSettings||{};return e&&t&&i&&i[e]&&i[e][g.JSON_MAPPING.ADSERVER_TARGETING]?(d(r,i[e],t),t.alwaysUseBid=i[e].alwaysUseBid):j[e]?(d(r,j[e],t),t.alwaysUseBid=j[e].alwaysUseBid):t&&i&&(i[g.JSON_MAPPING.BD_SETTING_STANDARD]||(i[g.JSON_MAPPING.BD_SETTING_STANDARD]={adserverTargeting:[{key:"hb_bidder",val:function(e){return e.bidderCode}},{key:"hb_adid",val:function(e){return e.adId}},{key:"hb_pb",val:function(e){return e.pbMg}},{key:"hb_size",val:function(e){return e.size}}]}),d(r,i[g.JSON_MAPPING.BD_SETTING_STANDARD],t)),r},r.registerDefaultBidderSetting=function(e,t){j[e]=t},r.registerBidRequestTime=function(e,t){D[e]=t},r.executeCallback=function(){if(typeof pbjs.registerBidCallbackHandler===h&&!A)try{pbjs.registerBidCallbackHandler(),A=!0}catch(e){A=!0,m.logError("Exception trying to execute callback handler registered : "+e.message)}if(_.called!==!0){var t=[];c(_,t),_.called=!0}if(B){var t=[],r=pbjs.getBidResponses();t.push(r),c(B,t),B=null}},r.allBidsBack=function(){return R},r.setBidderMap=function(e){I=e},r.checkIfAllBidsAreIn=function(e){R=l(),p(e),R&&this.executeCallback()},r.addOneTimeCallback=function(e){B=e},r.addCallback=function(e,t,r){t.id=e,g.CB.TYPE.ALL_BIDS_BACK===r?_.push(t):g.CB.TYPE.AD_UNIT_BIDS_BACK===r&&y.push(t)},b.on(g.EVENTS.BID_ADJUSTMENT,function(e){f(e)})},{"./adaptermanager":1,"./constants.json":16,"./events":17,"./utils.js":20}],16:[function(e,t,r){t.exports={JSON_MAPPING:{PL_CODE:"code",PL_SIZE:"sizes",PL_BIDS:"bids",BD_BIDDER:"bidder",BD_ID:"paramsd",BD_PL_ID:"placementId",ADSERVER_TARGETING:"adserverTargeting",BD_SETTING_STANDARD:"standard"},DEBUG_MODE:"pbjs_debug",STATUS:{GOOD:"good",TIMEOUT:"timed out"},CB:{TYPE:{ALL_BIDS_BACK:"allRequestedBidsBack", -AD_UNIT_BIDS_BACK:"adUnitBidsBack"}},objectType_function:"function",objectType_undefined:"undefined",objectType_object:"object",objectType_string:"string",objectType_number:"number",EVENTS:{BID_ADJUSTMENT:"bidAdjustment",BID_TIMEOUT:"bidTimeout",BID_REQUESTED:"bidRequested",BID_RESPONSE:"bidResponse",BID_WON:"bidWon"}}},{}],17:[function(e,t,r){var i=e("./utils"),n=e("./constants"),a=Array.prototype.slice,s=i._map(n.EVENTS,function(e){return e}),d=[];t.exports=function(){function e(e,t){i.logMessage("Emitting event for: "+e),d.push({eventType:e,args:t}),i._each(r[e],function(e){if(e)try{e.apply(null,t)}catch(r){i.logError("Error executing handler:","events.js",r)}})}function t(e){return i.contains(s,e)}var r={},n={};return n.on=function(e,n){t(e)?(r[e]=r[e]||[],r[e].push(n)):i.logError("Wrong event name : "+e+" Valid event names :"+s)},n.emit=function(t){var r=a.call(arguments,1);e(t,r)},n.off=function(e,t,n){i.isEmpty(r[e])||i._each(r[e],function(e){null!==e[t]&&void 0!==e[t]&&("undefined"==typeof n||e[t]===n)&&(e[t]=null)})},n.get=function(){return r},n.getEvents=function(){var e=[];return i._each(d,function(t){var r=i.extend({},t);e.push(r)}),e},n}()},{"./constants":16,"./utils":20}],18:[function(e,t,r){function i(){for(var e=0;e0&&i.push({cpm:s.cpm,bid:s}),t.push(n)}}if(r&&0!==i.length){var d=u(i),o=d.adserverTargeting;O[r]=_.extend(O[r],o)}return t}function g(e){var t={};if(e){var r=JSON.stringify(e);t=JSON.parse(r),delete t.pbLg,delete t.pbMg,delete t.pbHg}return t}function m(){B.clearAllBidResponses(),N={},U=[],O={},q=!1}function b(e){var t=e;m(),n(t)}function h(e){var t=null;return e&&(t=v.getAdserverTargetingForAdUnitCode(e.getSlotElementId()),t||(t=v.getAdserverTargetingForAdUnitCode(e.getAdUnitPath()))),t}window.pbjs=window.pbjs||{},window.pbjs.que=window.pbjs.que||[];var v=window.pbjs,y=e("./constants.json"),_=e("./utils.js"),B=e("./bidmanager.js"),I=e("./adaptermanager"),w=e("./bidfactory"),E=e("./adloader"),C=e("./ga"),T=e("./events"),S="function",R="undefined",A="object",j="string",D=y.EVENTS.BID_WON,P=y.EVENTS.BID_TIMEOUT,x=[],U=[],N={},O={},M={},q=!1;v.bidderTimeout=v.bidderTimeout||3e3,v.logging=v.logging||!1,v.libLoaded=!0,v.adUnits=v.adUnits||[],v.que.push=function(e){if(typeof e===S)try{e.call()}catch(t){_.logError("Error processing command :"+t.message)}else _.logError("Commands written into pbjs.que.push must wrapped in a function")},v.getAdserverTargetingForAdUnitCodeStr=function(e){if(e){var t=v.getAdserverTargetingForAdUnitCode(e);return _.transformAdServerTargetingObj(t)}_.logMessage("Need to call getAdserverTargetingForAdUnitCodeStr with adunitCode")},v.getAdserverTargetingForAdUnitCode=function(e){return v.getBidResponses(e),e?O[e]:O},v.getAdserverTargeting=function(){return v.getAdserverTargetingForAdUnitCode()},v.getBidResponses=function(e){var t={},r=[],i={};if(e)t=l(e),r=[],t&&t.bids&&(r=f(t.bids)),i={bids:r};else{t=l();for(var n in t)t.hasOwnProperty(n)&&(t&&t[n]&&t[n].bids&&(r=f(t[n].bids)),i[n]={bids:r})}return i},v.getBidResponsesForAdUnitCode=function(e){return v.getBidResponses(e)},v.setTargetingForAdUnitsGPTAsync=function(e){if(!window.googletag||!_.isFn(window.googletag.pubads)||!_.isFn(window.googletag.pubads().getSlots))return void _.logError("window.googletag is not defined on the page");s();var t=e;typeof e===j?t=[e]:typeof e===A&&(t=e);var r={},i=0;if(t)for(i=0;i'),e.close(),e.defaultView&&e.defaultView.frameElement&&(e.defaultView.frameElement.width=n,e.defaultView.frameElement.height=i)):_.logError("Error trying to write ad. No ad for bid response id: "+t)}else _.logError("Error trying to write ad. Cannot find ad by given id : "+t)}catch(d){_.logError("Error trying to write ad Id :"+t+" to the page:"+d.message)}else _.logError("Error trying to write ad Id :"+t+" to the page. Missing document or adId")},v.requestBidsForAdUnit=function(e){m(),n(e)},v.requestBidsForAdUnits=function(e){if(!e||e.constructor!==Array)return void _.logError("requestBidsForAdUnits must pass an array of adUnits");m();var t=v.adUnits.slice(0);v.adUnits=e,n(),v.adUnits=t},v.removeAdUnit=function(e){if(e)for(var t=0;t=0&&200>e?t="0-200ms":e>=200&&300>e?t="200-300ms":e>=300&&400>e?t="300-400ms":e>=400&&500>e?t="400-500ms":e>=500&&600>e?t="500-600ms":e>=600&&800>e?t="600-800ms":e>=800&&1e3>e?t="800-1000ms":e>=1e3&&1200>e?t="1000-1200ms":e>=1200&&1500>e?t="1200-1500ms":e>=1500&&2e3>e?t="1500-2000ms":e>=2e3&&(t="2000ms above"),t}function s(e){var t;return e>=0&&.5>e?t="$0-0.5":e>=.5&&1>e?t="$0.5-1":e>=1&&1.5>e?t="$1-1.5":e>=1.5&&2>e?t="$1.5-2":e>=2&&2.5>e?t="$2-2.5":e>=2.5&&3>e?t="$2.5-3":e>=3&&4>e?t="$3-4":e>=4&&6>e?t="$4-6":e>=6&&8>e?t="$6-8":e>=8&&(t="$8 above"),t}function d(e){e&&e.bidderCode&&y.push(function(){w++,window[_]("send","event",I,"Requests",e.bidderCode,1,v)}),i()}function o(e){e&&e.bidderCode&&y.push(function(){var t=n(e.cpm),r=e.bidderCode;if("undefined"!=typeof e.timeToRespond&&E){w++;var i=a(e.timeToRespond);window[_]("send","event","Prebid.js Load Time Distribution",i,r,1,v)}if(e.cpm>0){w+=2;var d=s(e.cpm);E&&(w++,window[_]("send","event","Prebid.js CPM Distribution",d,r,1,v)),window[_]("send","event",I,"Bids",r,t,v),window[_]("send","event",I,"Bid Load Time",r,e.timeToRespond,v)}}),i()}function c(e){e&&e.bidder&&y.push(function(){l._each(C,function(t){e.bidder===t&&(w++,window[_]("send","event",I,"Timeouts",t,e.timeToRespond,v))})}),i()}function u(e){var t=n(e.cpm);y.push(function(){w++,window[_]("send","event",I,"Wins",e.bidderCode,t,v)}),i()}var p=e("./events"),l=e("./utils"),f=e("./constants.json"),g=f.EVENTS.BID_REQUESTED,m=f.EVENTS.BID_TIMEOUT,b=f.EVENTS.BID_RESPONSE,h=f.EVENTS.BID_WON,v={nonInteraction:!0},y=[],_=null,B=!0,I="Prebid.js Bids",w=0,E=!1,C=[];r.enableAnalytics=function(e){_="undefined"!=typeof e.global?e.global:"ga","undefined"!=typeof e.enableDistribution&&(E=e.enableDistribution);var t=null,r=p.getEvents();l._each(r,function(e){var r=e.args;if(e)if(e.eventType===g)t=r[0],d(t);else if(e.eventType===b)t=r[1],o(t);else if(e.eventType===m){var i=r[0];C=i}else e.eventType===h&&(t=r[0],u(t))}),p.on(g,function(e){d(e)}),p.on(b,function(e,t){o(t),c(t)}),p.on(m,function(e){C=e}),p.on(h,function(e){u(e)})}},{"./constants.json":16,"./events":17,"./utils":20}],20:[function(e,t,r){function i(){return v()+Math.random().toString(16).substr(2)}function n(){return window.console&&window.console.log}{var a=e("./constants.json"),s="object",d="string",o="number",c=!1,u=5,p=20,l=20,f="Array",g="String",m="Function",b=Object.prototype.toString,h=Object.prototype.hasOwnProperty;Array.prototype.slice}r.replaceTokenInString=function(e,t,r){return this._each(t,function(t,i){t=void 0===t?"":t;var n=r+i.toUpperCase()+r,a=new RegExp(n,"g");e=e.replace(a,t)}),e};var v=function(){var e=0;return function(){return e++,e}}();r.getUniqueIdentifierStr=i,r.getBidIdParamater=function(e,t){return t&&t[e]?t[e]:""},r.tryAppendQueryString=function(e,t,r){return r?e+=t+"="+encodeURIComponent(r)+"&":e},r.parseQueryStringParameters=function(e){var t="";for(var r in e)e.hasOwnProperty(r)&&(t+=r+"="+encodeURIComponent(e[r])+"&");return t},r.transformAdServerTargetingObj=function(e){var t="";if(!e)return"";for(var r in e)e.hasOwnProperty(r)&&(t+=r+"="+encodeURIComponent(e[r])+"&");return t},r.extend=function(e,t){return e=e||{},this._each(t,function(r,i){e[i]=typeof t[i]===s?this.extend(e[i],t[i]):t[i]}),e},r.parseSizesInput=function(e){var t,r=[];if(typeof e===d){var i=e.split(","),n=/^(\d)+x(\d)+$/i;if(i)for(var a in i)I(i,a)&&i[a].match(n)&&r.push(i[a])}else if(typeof e===s){var c=e.length;if(c>0)if(2===c&&typeof e[0]===o&&typeof e[1]===o)r.push(this.parseGPTSingleSizeArray(e));else for(var u=0;c>u;u++)r.push(this.parseGPTSingleSizeArray(e[u]))}var p=r.length;if(p>0&&(t="size="+r[0],p>1)){t+="&promo_sizes=";for(var l=1;p>l;l++)t+=r[l]+=",";t&&","===t.charAt(t.length-1)&&(t=t.slice(0,t.length-1))}return t},r.parseGPTSingleSizeArray=function(e){return!this.isArray(e)||2!==e.length||isNaN(e[0])||isNaN(e[1])?void 0:e[0]+"x"+e[1]},r.getTopWindowUrl=function(){try{return window.top.location.href}catch(e){return window.location.href}},r.logMessage=function(e){_()&&n()&&console.log("MESSAGE: "+e)},r.hasConsoleLogger=n;var y=function(e){return e?window.console.error?"error":"log":""}(n()),_=function(){return pbjs.logging===!1&&c===!1&&(pbjs.logging="TRUE"===B(a.DEBUG_MODE).toUpperCase(),c=!0),pbjs.logging?!0:!1};r.debugTurnedOn=_,r.logError=function(e,t,r){var i=t||"ERROR";_()&&n()&&console[y].call(console,i+": "+e,r||"")},r.createInvisibleIframe=function(){var e=document.createElement("iframe");return e.id=i(),e.height=0,e.width=0,e.border="0px",e.hspace="0",e.vspace="0",e.marginWidth="0",e.marginHeight="0",e.style.border="0",e.scrolling="no",e.frameBorder="0",e.src="about:self",e.style="display:none",e};var B=function(e){var t="[\\?&]"+e+"=([^&#]*)",r=new RegExp(t),i=r.exec(window.location.search);return null===i?"":decodeURIComponent(i[1].replace(/\+/g," "))};r.getPriceBucketString=function(e){var t="",r="",i="",n=0,a={low:t,med:r,high:i};try{n=parseFloat(e),n&&(a.low=n>u?u.toFixed(2):(Math.floor(2*e)/2).toFixed(2),a.med=n>p?p.toFixed(2):(Math.floor(10*e)/10).toFixed(2),a.high=n>l?l.toFixed(2):(Math.floor(100*e)/100).toFixed(2))}catch(s){this.logError("Exception parsing CPM :"+s.message)}return a},r.hasValidBidRequest=function(e,t,r){for(var i=0;i0);for(var t in e)if(h.call(e,t))return!1;return!0},r._each=function(e,t){if(!this.isEmpty(e)){if(this.isFn(e.forEach))return e.forEach(t,this);var r=0,i=e.length;if(i>0)for(;i>r;r++)t(e[r],r,e);else for(r in e)h.call(e,r)&&t.call(this,e[r],r)}},r.contains=function(e,t){if(this.isEmpty(e))return!1;if(this.isFn(e.indexOf))return-1!==e.indexOf(t);for(var r=e.length;r--;)if(e[r]===t)return!0;return!1},r._map=function(e,t){if(this.isEmpty(e))return[];if(this.isFn(e.map))return e.map(t);var r=[];return this._each(e,function(i,n){r.push(t(i,n,e))}),r};var I=function(e,t){return e.hasOwnProperty?e.hasOwnProperty(t):typeof e[t]!==UNDEFINED&&e.constructor.prototype[t]!==e[t]}},{"./constants.json":16}]},{},[18]); \ No newline at end of file diff --git a/gulpHelpers.js b/gulpHelpers.js new file mode 100644 index 00000000000..b80bab06a0b --- /dev/null +++ b/gulpHelpers.js @@ -0,0 +1,14 @@ +module.exports = { + parseBrowserArgs: function(argv) { + return (argv.browsers) ? argv.browsers.split(',') : []; + }, + toCapitalCase: function(str) { + return str.charAt(0).toUpperCase() + str.slice(1); + }, + jsonifyHTML: function(str) { + console.log(arguments); + return str.replace(/\n/g, '') + .replace(/<\//g, '<\\/') + .replace(/\/>/g, '\\/>'); + } +}; \ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js index 1b8c43bfabf..5f51cb895c9 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,164 +1,127 @@ +var fs = require('fs'); +var argv = require('yargs').argv; var gulp = require('gulp'); +var gutil = require('gulp-util'); +var connect = require('gulp-connect'); +var webpack = require('gulp-webpack'); var uglify = require('gulp-uglify'); -var rename = require('gulp-rename'); var jshint = require('gulp-jshint'); -var jscs = require('gulp-jscs'); -var header = require('gulp-header'); +var clean = require('gulp-clean'); +var karma = require('gulp-karma'); +var replace = require('gulp-replace'); +var rename = require('gulp-rename'); +var opens = require('open'); +var webpackConfig = require('./webpack.conf.js'); +var helpers = require('./gulpHelpers'); +var path = require('path'); var del = require('del'); -var ecstatic = require('ecstatic'); -var gulpBrowserify = require('gulp-browserify'); -var gutil = require("gulp-util"); var gulpJsdoc2md = require("gulp-jsdoc-to-markdown"); var concat = require("gulp-concat"); +var jscs = require('gulp-jscs'); +var header = require('gulp-header'); var zip = require('gulp-zip'); -var mocha = require('gulp-mocha'); -var preprocessify = require('preprocessify'); -/** for automated unit testing */ -var browserSync = require("browser-sync"), - browserify = require("browserify"), - source = require("vinyl-source-stream"), - mochaPhantomJS = require("gulp-mocha-phantomjs"); - - -var releaseDir = './dist/'; -var csaSrcLocation = './src/prebid.js'; +var CI_MODE = process.env.NODE_ENV === 'ci'; +var prebidSrcLocation = './src/prebid.js'; var pkg = require('./package.json'); - var dateString = 'Updated : ' + (new Date()).toISOString().substring(0, 10); var packageNameVersion = pkg.name + '_' + pkg.version; - var banner = '/* <%= pkg.name %> v<%= pkg.version %> \n' + dateString + ' */\n'; -//basic/quick tests go here, to be run on every build -gulp.task('runBasicTests', function() { - return gulp.src('test/*', {read: false}) - .pipe(mocha()); -}); - +// Tasks +gulp.task('default', ['clean', 'quality', 'webpack']); -gulp.task('jscs', function() { - return gulp.src(csaSrcLocation) - .pipe(jscs({ - 'configPath': 'styleRules.jscs.json' - })); +gulp.task('serve', ['clean', 'quality', 'webpack', 'watch', 'test']); +gulp.task('build', ['clean', 'quality', 'webpack', 'zip']); +gulp.task('clean', function() { + return gulp.src(['build', 'test/app/**/*.js', 'test/app/index.html'], { + read: false + }) + .pipe(clean()); }); -//run code quality checks here -gulp.task('jshint', function() { - - gulp.src(['./src/*.js', './src/adapters/*.js']) - .pipe(jshint({ - 'bitwise': 'true', - 'curly': 'true', - 'scripturl': 'false', - //'enforceall':'false', - //since we are never orriding Prootype (we control all this code) - //I am ok letting this be false in the name of faster code execution - 'forin': false, - 'eqeqeq': true, - //'es3':true, - //'es5':true, - 'freeze': true, - 'futurehostile': true, - 'latedef': true, - 'maxerr': '1000', - 'noarg': true, - 'nocomma': true, - 'nonbsp': true, - 'nonew': true, - 'notypeof': true, - //excessive parens are ok as long as they increase code readability - //and help to prevent errors, especially when helping to provide - //structure to long conditonal statements - 'singleGroups': false, - 'undef': true, - 'unused': true, - 'globals': { - 'require': false, - 'module' : true, - 'exports' : true, - 'pbjs' : true, - 'googletag': true, - 'ActiveXObject': true - }, - 'browser': true, - 'devel': true - - })) - .pipe(jshint.reporter('jshint-stylish')) - .pipe(jshint.reporter('fail')); - -}); +gulp.task('webpack', function() { -gulp.task('clean-dist', function(cb) { - del([releaseDir + '']); - cb(); + // change output filename if argument --tag given + if (argv.tag && argv.tag.length) { + webpackConfig.output.filename = 'prebid.' + argv.tag + '.js'; + } + return gulp.src('src/**/*.js') + .pipe(webpack(webpackConfig)) + .pipe(header(banner, {pkg: pkg})) + .pipe(gulp.dest('build/dev')) + .pipe(gulp.dest('test/app')) + .pipe(uglify()) + .pipe(gulp.dest('build/dist')) + .pipe(connect.reload()); }); -gulp.task('codeQuality', ['jshint', 'jscs'], function() {}); - - -gulp.task('default', ['build'], function() {}); +//zip up for release +gulp.task('zip', ['jscs', 'clean', 'webpack'], function () { + return gulp.src(['build/dist/*', 'integrationExamples/gpt/*']) + .pipe(zip(packageNameVersion + '.zip')) + .pipe(gulp.dest('./')); +}); -gulp.task('serve', ['build-dev', 'watch', 'browser-sync'], function () { - var port = 9999; - require('http').createServer(ecstatic({ - root: __dirname - })).listen(port); - console.log('Server started at http://localhost:' + port + '/'); +// Karma Continuous Testing +// Pass your browsers by using --browsers=chrome,firefox,ie9 +// Run CI by passing --watch +gulp.task('test', function() { + var defaultBrowsers = CI_MODE ? ['PhantomJS'] : ['Chrome']; + var browserArgs = helpers.parseBrowserArgs(argv).map(helpers.toCapitalCase); + + return gulp.src('lookAtKarmaConfJS') + .pipe(karma({ + browsers: (browserArgs.length > 0) ? browserArgs : defaultBrowsers, + configFile: 'karma.conf.js', + action: (argv.watch) ? 'watch' : 'run' + })); }); -gulp.task('build-dev', ['jscs', 'clean-dist', 'browserify', 'unit-tests'], function () { - gulp.src(['src/prebid.js']) - .pipe(gulpBrowserify({ - debug: false - })) - .pipe(header(banner, { - pkg: pkg - })) - .pipe(gulp.dest(releaseDir)); +// Small task to load coverage reports in the browser +gulp.task('coverage', function(done) { + var coveragePort = 1999; + connect.server({ + port: 1999, + root: 'build/coverage', + livereload: false + }); + opens('http://localhost:' + coveragePort + '/coverage/'); + done(); }); -gulp.task('build-minify', ['clean-dist', 'quality'], function(cb){ - - gulp.src(['src/prebid.js']) - .pipe(gulpBrowserify({ - transform : preprocessify({ NODE_ENV: 'production'}) - } +// Watch Task with Live Reload +gulp.task('watch', function() { - )) - //unminified version: - .pipe(header(banner, {pkg: pkg})) - .pipe(gulp.dest(releaseDir)) - //minified version - .pipe(uglify()) - .pipe(header(banner, {pkg: pkg})) - .pipe(rename({ - basename: 'prebid.min', - extname: '.js' - })) - .pipe(gulp.dest(releaseDir)); - //notify that release is ready - cb(); + gulp.watch(['test/spec/**/*.js'], ['webpack', 'test']); + gulp.watch(['integrationExamples/gpt/*.html'], ['test']); + gulp.watch(['src/**/*.js'], ['webpack', 'test']); + connect.server({ + port: 9999, + root: './', + livereload: true + }); }); -gulp.task('build', ['clean-dist', 'quality', 'build-minify', 'zip']); +gulp.task('quality', ['hint', 'jscs'], function() {}); -gulp.task('quality', ['jscs'], function(cb){ - cb(); +gulp.task('hint', function() { + return gulp.src('src/**/*.js') + .pipe(jshint('.jshintrc')) + .pipe(jshint.reporter('default')); }); -gulp.task('watch', function () { - // gulp.watch(['src/**/*.js'], ['build-dev']); - gulp.watch(["test/tests.js", "src/*","test/spec/*.js"], ["browserify", "unit-tests"]); +gulp.task('jscs', function() { + return gulp.src(prebidSrcLocation) + .pipe(jscs({ + 'configPath': 'styleRules.jscs.json' + })); }); - gulp.task('clean-docs', function(){ del(['docs']); }); @@ -172,50 +135,4 @@ gulp.task("docs", ['clean-docs'], function() { }) .pipe(gulp.dest("docs")); }); - -//zip up for release -gulp.task('zip', ['quality', 'clean-dist', 'build-minify'], function () { - return gulp.src(['dist/*', 'integrationExamples/gpt/*']) - .pipe(zip(packageNameVersion + '.zip')) - .pipe(gulp.dest('./')); -}); -gulp.task("browser-sync", function () { - "use strict"; - browserSync({ - server: { - //serve tests and the root as base dirs - baseDir: ["./test/", "./"], - //make tests.html the index file - index: "automatedRunnner.html" - } - }); -}); - -//see http://fettblog.eu/gulp-browserify-multiple-bundles/ -gulp.task("browserify", function() { - "use strict"; - var files =[ - "./test/test.js", - "./test/spec/api_spec.js", - "./test/spec/utils_spec.js", - "./test/spec/adUnits_spec.js", - "./test/spec/aliasBidder_spec.js" - ]; - - return browserify({ entries: [files] }) - .bundle() - .on("error", function (err) { - console.log(err.toString()); - this.emit("end"); - }) - .pipe(source("tests-browserify.js")) - .pipe(gulp.dest("test/")) - .pipe(browserSync.reload({stream:true})); -}); - -gulp.task("unit-tests", function () { - "use strict"; - return gulp.src("./test/automatedRunnner.html") - .pipe(mochaPhantomJS()); -}); diff --git a/integrationExamples/gpt/gpt_aliasingBidder.html b/integrationExamples/gpt/gpt_aliasingBidder.html index 4d6c2833a46..979343a382e 100644 --- a/integrationExamples/gpt/gpt_aliasingBidder.html +++ b/integrationExamples/gpt/gpt_aliasingBidder.html @@ -37,7 +37,7 @@ (function() { var d = document, pbs = d.createElement("script"), pro = d.location.protocal; pbs.type = "text/javascript"; - pbs.src = '/dist/prebid.js'; + pbs.src = '/build/dist/prebid.js'; var target = document.getElementsByTagName("head")[0]; target.insertBefore(pbs, target.firstChild); })(); diff --git a/integrationExamples/gpt/pbjs_example_gpt.html b/integrationExamples/gpt/pbjs_example_gpt.html index 24196e5aa51..6e192a7ad66 100644 --- a/integrationExamples/gpt/pbjs_example_gpt.html +++ b/integrationExamples/gpt/pbjs_example_gpt.html @@ -38,7 +38,7 @@ (function() { var d = document, pbs = d.createElement("script"), pro = d.location.protocal; pbs.type = "text/javascript"; - pbs.src = '/dist/prebid.js'; + pbs.src = '/build/dist/prebid.js'; var target = document.getElementsByTagName("head")[0]; target.insertBefore(pbs, target.firstChild); })(); diff --git a/integrationExamples/gpt/pbjs_partial_refresh_gpt.html b/integrationExamples/gpt/pbjs_partial_refresh_gpt.html index 8445d0cf923..1cf631e640c 100644 --- a/integrationExamples/gpt/pbjs_partial_refresh_gpt.html +++ b/integrationExamples/gpt/pbjs_partial_refresh_gpt.html @@ -38,7 +38,7 @@ (function() { var d = document, pbs = d.createElement("script"), pro = d.location.protocal; pbs.type = "text/javascript"; - pbs.src = 'prebid.0.3.2.min.js'; + pbs.src = '/build/dist/prebid.js'; var target = document.getElementsByTagName("head")[0]; target.insertBefore(pbs, target.firstChild); })(); diff --git a/karma.conf.js b/karma.conf.js new file mode 100644 index 00000000000..d04d1e30f01 --- /dev/null +++ b/karma.conf.js @@ -0,0 +1,110 @@ +// Karma configuration +// Generated on Thu Aug 07 2014 09:45:28 GMT-0700 (PDT) +var webpackConfig = require('./webpack.conf'); +webpackConfig.module.postLoaders = [{ + test: /\.js$/, + exclude: /(mediation-script)|(node_modules)|(test)|(polyfill)|(visibly)/, + loader: 'istanbul-instrumenter' +}]; + +var CI_MODE = process.env.NODE_ENV === 'ci'; + +module.exports = function(config) { + config.set({ + + // base path that will be used to resolve all patterns (eg. files, exclude) + basePath: './', + + // frameworks to use + // available frameworks: https://npmjs.org/browse/keyword/karma-adapter + frameworks: ['es5-shim', 'mocha', 'expect', 'sinon'], + + // list of files / patterns to load in the browser + files: [ + 'test/**/*_spec.js' + ], + + // list of files to exclude + exclude: [], + + // preprocess matching files before serving them to the browser + // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor + preprocessors: { + 'test/**/*_spec.js': ['webpack'], + 'src/**/*.js': ['coverage'] + }, + + // WebPack Related + webpack: webpackConfig, + + // test results reporter to use + // possible values: 'dots', 'progress' + // available reporters: https://npmjs.org/browse/keyword/karma-reporter + reporters: CI_MODE ? ['junit', 'coverage'] : ['progress', 'html', 'nyan', 'coverage'], + + // junit reporter config + junitReporter: { + outputDir: 'test' + }, + + // optionally, configure the reporter + coverageReporter: { + reporters: [ + { type: 'html', dir:'./build/coverage/' }, + { type: 'text', dir:'./build/coverage/' }, + { type: 'lcov', dir:'./build/coverage/lcov', subdir: '.' } + ] + }, + + htmlReporter: { + outputDir: 'build/coverage/karma_html', // where to put the reports + urlFriendlyName: true, // simply replaces spaces with _ for files/dirs + reportName: 'report' // report summary filename; browser info by default + }, + + // web server port + port: 9876, + + // enable / disable colors in the output (reporters and logs) + colors: true, + + // level of logging + // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG + logLevel: config.LOG_INFO, + + + // enable / disable watching file and executing tests whenever any file changes + autoWatch: true, + + + // start these browsers + // NOTE: these get defined again in gulpfile.js for the gulp tasks + // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher + browsers: ['Chrome', 'Firefox'], + + // Continuous Integration mode + // if true, Karma captures browsers, runs the tests and exits + singleRun: false, + + plugins: [ + 'karma-phantomjs-launcher', + 'karma-nyan-reporter', + 'karma-coverage', + 'karma-es5-shim', + 'karma-mocha', + 'karma-expect', + 'karma-sinon', + 'karma-webpack', + 'karma-junit-reporter', + 'karma-html-reporter', + 'karma-chrome-launcher', + 'karma-sauce-launcher', + 'karma-firefox-launcher', + 'karma-opera-launcher', + 'karma-safari-launcher', + 'karma-script-launcher', + 'karma-requirejs', + 'karma-ie-launcher' + ] + }); +}; \ No newline at end of file diff --git a/package.json b/package.json index 029abcc693b..067e79df0d3 100644 --- a/package.json +++ b/package.json @@ -3,38 +3,77 @@ "version": "0.5.0", "description": "Header Bidding Management Library", "main": "prebid.js", - "scripts": {}, + "scripts": { + "test": "gulp test" + }, "author": [], "license": "Apache-2.0", "devDependencies": { - "browser-sync": "^2.11.0", - "browserify": "^12.0.1", - "del": "^1.1.1", - "ecstatic": "^0.8.0", - "gulp": "^3.9.0", - "gulp-beautify": "^1.1.2", - "gulp-browserify": "^0.5.1", + "chai": "^3.3.0", + "chai-as-promised": "^5.1.0", + "clone": "^0.1.17", + "es5-shim": "^4.5.2", + "eslint": "^1.9.0", + "express": "^4.10.2", + "fuzzyset.js": "0.0.1", + "gulp": "^3.8.7", + "gulp-clean": "^0.3.1", "gulp-concat": "^2.6.0", - "gulp-header": "^1.2.2", - "gulp-jscs": "^1.4.0", - "gulp-jsdoc": "^0.1.4", - "gulp-jsdoc-to-markdown": "^1.1.1", - "gulp-jshint": "^1.9.2", - "gulp-mocha": "^2.1.3", - "gulp-mocha-phantomjs": "^0.10.1", - "gulp-notify": "^2.2.0", - "gulp-rename": "^1.2.2", - "gulp-sourcemaps": "^1.5.0", - "gulp-strip-comments": "^1.0.1", - "gulp-uglify": "^1.1.0", - "gulp-util": "^3.0.6", - "gulp-zip": "^3.0.2", - "jsdoc-to-markdown": "^1.1.1", - "jshint-stylish": "^2.0.1", - "mocha": "^2.3.4", - "mocha-phantomjs": "^4.0.2", - "preprocessify": "0.0.6", - "vinyl-source-stream": "^1.1.0" + "gulp-connect": "^2.0.6", + "gulp-header": "^1.7.1", + "gulp-jscs": "^3.0.2", + "gulp-jsdoc-to-markdown": "^1.2.1", + "gulp-jshint": "^1.8.4", + "gulp-karma": "0.0.4", + "gulp-rename": "^1.2.0", + "gulp-replace": "^0.4.0", + "gulp-uglify": "^0.3.1", + "gulp-util": "^3.0.0", + "gulp-webdriver": "^1.0.1", + "gulp-webpack": "0.3.0", + "gulp-zip": "^3.1.0", + "istanbul": "^0.3.2", + "istanbul-instrumenter-loader": "^0.1.2", + "jquery": "1.11.1", + "json-loader": "^0.5.1", + "karma": "^0.13.2", + "karma-chai": "^0.1.0", + "karma-chrome-launcher": "^0.2.0", + "karma-coverage": "^0.2.6", + "karma-es5-shim": "https://github.com/pokehanai/karma-es5-shim/archive/v2.1.0.tar.gz", + "karma-expect": "^1.1.0", + "karma-firefox-launcher": "^0.1.3", + "karma-html-reporter": "^0.2.7", + "karma-ie-launcher": "^0.1.5", + "karma-junit-reporter": "^0.3.8", + "karma-mocha": "^0.2.0", + "karma-nyan-reporter": "0.2.2", + "karma-opera-launcher": "^0.1.0", + "karma-phantomjs-launcher": "^0.2.1", + "karma-requirejs": "^0.2.2", + "karma-safari-launcher": "^0.1.1", + "karma-sauce-launcher": "^0.2.10", + "karma-script-launcher": "^0.1.0", + "karma-sinon": "^1.0.4", + "karma-webpack": "^1.5.1", + "localtunnel": "^1.3.0", + "lodash": "^2.4.1", + "mocha": "^1.21.4", + "open": "0.0.5", + "phantomjs": "^1.9.18", + "raw-loader": "^0.5.1", + "redis": "^0.12.1", + "requirejs": "^2.1.20", + "rewire": "^2.1.0", + "rewire-webpack": "~1.0", + "run-sequence": "^1.1.4", + "sinon": "^1.12.1", + "uglify-js": "^2.4.15", + "webdriverio": "^3.2.5", + "webpack": "^1.12.3", + "webpack-dev-server": "^1.12.1", + "weinre": "~2.0.0-pre-I0Z7U9OV", + "yargs": "^1.3.1" }, "dependencies": {} } diff --git a/src/prebid.js b/src/prebid.js index 62ead6e4d63..d2957293017 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -128,9 +128,8 @@ function timeOutBidders(){ function sortAndCallBids(sortFunc) { //Translate the bidder map into array so we can sort later if wanted - var pbArr = Object.keys(pb_bidderMap).map(function(key) { - return pb_bidderMap[key]; - }); + var pbArr = utils._map(pb_bidderMap, function(v, k) { return v; }); + if (typeof sortFunc === objectType_function) { pbArr.sort(sortFunc); } diff --git a/test/automatedRunnner.html b/test/automatedRunnner.html deleted file mode 100644 index 2f82a9a36da..00000000000 --- a/test/automatedRunnner.html +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - Unit Tests for Prebid.js - - - -
- - - - - - - - - - - \ No newline at end of file diff --git a/test/lib/browser/jquery.js b/test/lib/browser/jquery.js deleted file mode 100755 index ee0233703da..00000000000 --- a/test/lib/browser/jquery.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery v1.7.1 jquery.com | jquery.org/license */ -(function(a,b){function cy(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cv(a){if(!ck[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){cl||(cl=c.createElement("iframe"),cl.frameBorder=cl.width=cl.height=0),b.appendChild(cl);if(!cm||!cl.createElement)cm=(cl.contentWindow||cl.contentDocument).document,cm.write((c.compatMode==="CSS1Compat"?"":"")+""),cm.close();d=cm.createElement(a),cm.body.appendChild(d),e=f.css(d,"display"),b.removeChild(cl)}ck[a]=e}return ck[a]}function cu(a,b){var c={};f.each(cq.concat.apply([],cq.slice(0,b)),function(){c[this]=a});return c}function ct(){cr=b}function cs(){setTimeout(ct,0);return cr=f.now()}function cj(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ci(){try{return new a.XMLHttpRequest}catch(b){}}function cc(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g0){if(c!=="border")for(;g=0===c})}function S(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function K(){return!0}function J(){return!1}function n(a,b,c){var d=b+"defer",e=b+"queue",g=b+"mark",h=f._data(a,d);h&&(c==="queue"||!f._data(a,e))&&(c==="mark"||!f._data(a,g))&&setTimeout(function(){!f._data(a,e)&&!f._data(a,g)&&(f.removeData(a,d,!0),h.fire())},0)}function m(a){for(var b in a){if(b==="data"&&f.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function l(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(k,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNumeric(d)?parseFloat(d):j.test(d)?f.parseJSON(d):d}catch(g){}f.data(a,c,d)}else d=b}return d}function h(a){var b=g[a]={},c,d;a=a.split(/\s+/);for(c=0,d=a.length;c)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,n=/^[\],:{}\s]*$/,o=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,p=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,q=/(?:^|:|,)(?:\s*\[)+/g,r=/(webkit)[ \/]([\w.]+)/,s=/(opera)(?:.*version)?[ \/]([\w.]+)/,t=/(msie) ([\w.]+)/,u=/(mozilla)(?:.*? rv:([\w.]+))?/,v=/-([a-z]|[0-9])/ig,w=/^-ms-/,x=function(a,b){return(b+"").toUpperCase()},y=d.userAgent,z,A,B,C=Object.prototype.toString,D=Object.prototype.hasOwnProperty,E=Array.prototype.push,F=Array.prototype.slice,G=String.prototype.trim,H=Array.prototype.indexOf,I={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=m.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.7.1",length:0,size:function(){return this.length},toArray:function(){return F.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?E.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),A.add(a);return this},eq:function(a){a=+a;return a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(F.apply(this,arguments),"slice",F.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:E,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j0)return;A.fireWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").off("ready")}},bindReady:function(){if(!A){A=e.Callbacks("once memory");if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",B,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",B),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&J()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a&&typeof a=="object"&&"setInterval"in a},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):I[C.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;try{if(a.constructor&&!D.call(a,"constructor")&&!D.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||D.call(a,d)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw new Error(a)},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(n.test(b.replace(o,"@").replace(p,"]").replace(q,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(c){var d,f;try{a.DOMParser?(f=new DOMParser,d=f.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(g){d=b}(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&e.error("Invalid XML: "+c);return d},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(w,"ms-").replace(v,x)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i1?i.call(arguments,0):b,j.notifyWith(k,e)}}function l(a){return function(c){b[a]=arguments.length>1?i.call(arguments,0):c,--g||j.resolveWith(j,b)}}var b=i.call(arguments,0),c=0,d=b.length,e=Array(d),g=d,h=d,j=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred(),k=j.promise();if(d>1){for(;c
a",d=q.getElementsByTagName("*"),e=q.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=q.getElementsByTagName("input")[0],b={leadingWhitespace:q.firstChild.nodeType===3,tbody:!q.getElementsByTagName("tbody").length,htmlSerialize:!!q.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:q.className!=="t",enctype:!!c.createElement("form").enctype,html5Clone:c.createElement("nav").cloneNode(!0).outerHTML!=="<:nav>",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0},i.checked=!0,b.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,b.optDisabled=!h.disabled;try{delete q.test}catch(s){b.deleteExpando=!1}!q.addEventListener&&q.attachEvent&&q.fireEvent&&(q.attachEvent("onclick",function(){b.noCloneEvent=!1}),q.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),b.radioValue=i.value==="t",i.setAttribute("checked","checked"),q.appendChild(i),k=c.createDocumentFragment(),k.appendChild(q.lastChild),b.checkClone=k.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=i.checked,k.removeChild(i),k.appendChild(q),q.innerHTML="",a.getComputedStyle&&(j=c.createElement("div"),j.style.width="0",j.style.marginRight="0",q.style.width="2px",q.appendChild(j),b.reliableMarginRight=(parseInt((a.getComputedStyle(j,null)||{marginRight:0}).marginRight,10)||0)===0);if(q.attachEvent)for(o in{submit:1,change:1,focusin:1})n="on"+o,p=n in q,p||(q.setAttribute(n,"return;"),p=typeof q[n]=="function"),b[o+"Bubbles"]=p;k.removeChild(q),k=g=h=j=q=i=null,f(function(){var a,d,e,g,h,i,j,k,m,n,o,r=c.getElementsByTagName("body")[0];!r||(j=1,k="position:absolute;top:0;left:0;width:1px;height:1px;margin:0;",m="visibility:hidden;border:0;",n="style='"+k+"border:5px solid #000;padding:0;'",o="
"+""+"
",a=c.createElement("div"),a.style.cssText=m+"width:0;height:0;position:static;top:0;margin-top:"+j+"px",r.insertBefore(a,r.firstChild),q=c.createElement("div"),a.appendChild(q),q.innerHTML="
t
",l=q.getElementsByTagName("td"),p=l[0].offsetHeight===0,l[0].style.display="",l[1].style.display="none",b.reliableHiddenOffsets=p&&l[0].offsetHeight===0,q.innerHTML="",q.style.width=q.style.paddingLeft="1px",f.boxModel=b.boxModel=q.offsetWidth===2,typeof q.style.zoom!="undefined"&&(q.style.display="inline",q.style.zoom=1,b.inlineBlockNeedsLayout=q.offsetWidth===2,q.style.display="",q.innerHTML="
",b.shrinkWrapBlocks=q.offsetWidth!==2),q.style.cssText=k+m,q.innerHTML=o,d=q.firstChild,e=d.firstChild,h=d.nextSibling.firstChild.firstChild,i={doesNotAddBorder:e.offsetTop!==5,doesAddBorderForTableAndCells:h.offsetTop===5},e.style.position="fixed",e.style.top="20px",i.fixedPosition=e.offsetTop===20||e.offsetTop===15,e.style.position=e.style.top="",d.style.overflow="hidden",d.style.position="relative",i.subtractsBorderForOverflowNotVisible=e.offsetTop===-5,i.doesNotIncludeMarginInBodyOffset=r.offsetTop!==j,r.removeChild(a),q=a=null,f.extend(b,i))});return b}();var j=/^(?:\{.*\}|\[.*\])$/,k=/([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!m(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g,h,i,j=f.expando,k=typeof c=="string",l=a.nodeType,m=l?f.cache:a,n=l?a[j]:a[j]&&j,o=c==="events";if((!n||!m[n]||!o&&!e&&!m[n].data)&&k&&d===b)return;n||(l?a[j]=n=++f.uuid:n=j),m[n]||(m[n]={},l||(m[n].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?m[n]=f.extend(m[n],c):m[n].data=f.extend(m[n].data,c);g=h=m[n],e||(h.data||(h.data={}),h=h.data),d!==b&&(h[f.camelCase(c)]=d);if(o&&!h[c])return g.events;k?(i=h[c],i==null&&(i=h[f.camelCase(c)])):i=h;return i}},removeData:function(a,b,c){if(!!f.acceptData(a)){var d,e,g,h=f.expando,i=a.nodeType,j=i?f.cache:a,k=i?a[h]:h;if(!j[k])return;if(b){d=c?j[k]:j[k].data;if(d){f.isArray(b)||(b in d?b=[b]:(b=f.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,g=b.length;e-1)return!0;return!1},val:function(a){var c,d,e,g=this[0];{if(!!arguments.length){e=f.isFunction(a);return this.each(function(d){var g=f(this),h;if(this.nodeType===1){e?h=a.call(this,d,g.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.nodeName.toLowerCase()]||f.valHooks[this.type];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}if(g){c=f.valHooks[g.nodeName.toLowerCase()]||f.valHooks[g.type];if(c&&"get"in c&&(d=c.get(g,"value"))!==b)return d;d=g.value;return typeof d=="string"?d.replace(q,""):d==null?"":d}}}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,g=a.selectedIndex,h=[],i=a.options,j=a.type==="select-one";if(g<0)return null;c=j?g:0,d=j?g+1:i.length;for(;c=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attr:function(a,c,d,e){var g,h,i,j=a.nodeType;if(!!a&&j!==3&&j!==8&&j!==2){if(e&&c in f.attrFn)return f(a)[c](d);if(typeof a.getAttribute=="undefined")return f.prop(a,c,d);i=j!==1||!f.isXMLDoc(a),i&&(c=c.toLowerCase(),h=f.attrHooks[c]||(u.test(c)?x:w));if(d!==b){if(d===null){f.removeAttr(a,c);return}if(h&&"set"in h&&i&&(g=h.set(a,d,c))!==b)return g;a.setAttribute(c,""+d);return d}if(h&&"get"in h&&i&&(g=h.get(a,c))!==null)return g;g=a.getAttribute(c);return g===null?b:g}},removeAttr:function(a,b){var c,d,e,g,h=0;if(b&&a.nodeType===1){d=b.toLowerCase().split(p),g=d.length;for(;h=0}})});var z=/^(?:textarea|input|select)$/i,A=/^([^\.]*)?(?:\.(.+))?$/,B=/\bhover(\.\S+)?\b/,C=/^key/,D=/^(?:mouse|contextmenu)|click/,E=/^(?:focusinfocus|focusoutblur)$/,F=/^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/,G=function(a){var b=F.exec(a);b&&(b[1]=(b[1]||"").toLowerCase(),b[3]=b[3]&&new RegExp("(?:^|\\s)"+b[3]+"(?:\\s|$)"));return b},H=function(a,b){var c=a.attributes||{};return(!b[1]||a.nodeName.toLowerCase()===b[1])&&(!b[2]||(c.id||{}).value===b[2])&&(!b[3]||b[3].test((c["class"]||{}).value))},I=function(a){return f.event.special.hover?a:a.replace(B,"mouseenter$1 mouseleave$1")}; -f.event={add:function(a,c,d,e,g){var h,i,j,k,l,m,n,o,p,q,r,s;if(!(a.nodeType===3||a.nodeType===8||!c||!d||!(h=f._data(a)))){d.handler&&(p=d,d=p.handler),d.guid||(d.guid=f.guid++),j=h.events,j||(h.events=j={}),i=h.handle,i||(h.handle=i=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.dispatch.apply(i.elem,arguments):b},i.elem=a),c=f.trim(I(c)).split(" ");for(k=0;k=0&&(h=h.slice(0,-1),k=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i.shift(),i.sort());if((!e||f.event.customEvent[h])&&!f.event.global[h])return;c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.isTrigger=!0,c.exclusive=k,c.namespace=i.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)"):null,o=h.indexOf(":")<0?"on"+h:"";if(!e){j=f.cache;for(l in j)j[l].events&&j[l].events[h]&&f.event.trigger(c,d,j[l].handle.elem,!0);return}c.result=b,c.target||(c.target=e),d=d!=null?f.makeArray(d):[],d.unshift(c),p=f.event.special[h]||{};if(p.trigger&&p.trigger.apply(e,d)===!1)return;r=[[e,p.bindType||h]];if(!g&&!p.noBubble&&!f.isWindow(e)){s=p.delegateType||h,m=E.test(s+h)?e:e.parentNode,n=null;for(;m;m=m.parentNode)r.push([m,s]),n=m;n&&n===e.ownerDocument&&r.push([n.defaultView||n.parentWindow||a,s])}for(l=0;le&&i.push({elem:this,matches:d.slice(e)});for(j=0;j0?this.on(b,null,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0),C.test(b)&&(f.event.fixHooks[b]=f.event.keyHooks),D.test(b)&&(f.event.fixHooks[b]=f.event.mouseHooks)}),function(){function x(a,b,c,e,f,g){for(var h=0,i=e.length;h0){k=j;break}}j=j[a]}e[h]=k}}}function w(a,b,c,e,f,g){for(var h=0,i=e.length;h+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d="sizcache"+(Math.random()+"").replace(".",""),e=0,g=Object.prototype.toString,h=!1,i=!0,j=/\\/g,k=/\r\n/g,l=/\W/;[0,0].sort(function(){i=!1;return 0});var m=function(b,d,e,f){e=e||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return e;var i,j,k,l,n,q,r,t,u=!0,v=m.isXML(d),w=[],x=b;do{a.exec(""),i=a.exec(x);if(i){x=i[3],w.push(i[1]);if(i[2]){l=i[3];break}}}while(i);if(w.length>1&&p.exec(b))if(w.length===2&&o.relative[w[0]])j=y(w[0]+w[1],d,f);else{j=o.relative[w[0]]?[d]:m(w.shift(),d);while(w.length)b=w.shift(),o.relative[b]&&(b+=w.shift()),j=y(b,j,f)}else{!f&&w.length>1&&d.nodeType===9&&!v&&o.match.ID.test(w[0])&&!o.match.ID.test(w[w.length-1])&&(n=m.find(w.shift(),d,v),d=n.expr?m.filter(n.expr,n.set)[0]:n.set[0]);if(d){n=f?{expr:w.pop(),set:s(f)}:m.find(w.pop(),w.length===1&&(w[0]==="~"||w[0]==="+")&&d.parentNode?d.parentNode:d,v),j=n.expr?m.filter(n.expr,n.set):n.set,w.length>0?k=s(j):u=!1;while(w.length)q=w.pop(),r=q,o.relative[q]?r=w.pop():q="",r==null&&(r=d),o.relative[q](k,r,v)}else k=w=[]}k||(k=j),k||m.error(q||b);if(g.call(k)==="[object Array]")if(!u)e.push.apply(e,k);else if(d&&d.nodeType===1)for(t=0;k[t]!=null;t++)k[t]&&(k[t]===!0||k[t].nodeType===1&&m.contains(d,k[t]))&&e.push(j[t]);else for(t=0;k[t]!=null;t++)k[t]&&k[t].nodeType===1&&e.push(j[t]);else s(k,e);l&&(m(l,h,e,f),m.uniqueSort(e));return e};m.uniqueSort=function(a){if(u){h=i,a.sort(u);if(h)for(var b=1;b0},m.find=function(a,b,c){var d,e,f,g,h,i;if(!a)return[];for(e=0,f=o.order.length;e":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!l.test(b)){b=b.toLowerCase();for(;e=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(j,"")},TAG:function(a,b){return a[1].replace(j,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||m.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&m.error(a[0]);a[0]=e++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(j,"");!f&&o.attrMap[g]&&(a[1]=o.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(j,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=m(b[3],null,null,c);else{var g=m.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(o.match.POS.test(b[0])||o.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!m(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return bc[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=o.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||n([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||!!a.nodeName&&a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=m.attr?m.attr(a,c):o.attrHandle[c]?o.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":!f&&m.attr?d!=null:f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=o.setFilters[e];if(f)return f(a,c,b,d)}}},p=o.match.POS,q=function(a,b){return"\\"+(b-0+1)};for(var r in o.match)o.match[r]=new RegExp(o.match[r].source+/(?![^\[]*\])(?![^\(]*\))/.source),o.leftMatch[r]=new RegExp(/(^(?:.|\r|\n)*?)/.source+o.match[r].source.replace(/\\(\d+)/g,q));var s=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(t){s=function(a,b){var c=0,d=b||[];if(g.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var e=a.length;c",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(o.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},o.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(o.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(o.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=m,b=c.createElement("div"),d="__sizzle__";b.innerHTML="

";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){m=function(b,e,f,g){e=e||c;if(!g&&!m.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return s(e.getElementsByTagName(b),f);if(h[2]&&o.find.CLASS&&e.getElementsByClassName)return s(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return s([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return s([],f);if(i.id===h[3])return s([i],f)}try{return s(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var k=e,l=e.getAttribute("id"),n=l||d,p=e.parentNode,q=/^\s*[+~]/.test(b);l?n=n.replace(/'/g,"\\$&"):e.setAttribute("id",n),q&&p&&(e=e.parentNode);try{if(!q||p)return s(e.querySelectorAll("[id='"+n+"'] "+b),f)}catch(r){}finally{l||k.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)m[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}m.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!m.isXML(a))try{if(e||!o.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return m(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="
";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;o.order.splice(1,0,"CLASS"),o.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?m.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?m.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:m.contains=function(){return!1},m.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var y=function(a,b,c){var d,e=[],f="",g=b.nodeType?[b]:b;while(d=o.match.PSEUDO.exec(a))f+=d[0],a=a.replace(o.match.PSEUDO,"");a=o.relative[a]?a+"*":a;for(var h=0,i=g.length;h0)for(h=g;h=0:f.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h=1;while(g&&g.ownerDocument&&g!==b){for(d=0;d-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a)return this[0]&&this[0].parentNode?this.prevAll().length:-1;if(typeof a=="string")return f.inArray(this[0],f(a));return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(S(c[0])||S(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling(a.parentNode.firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c);L.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!R[a]?f.unique(e):e,(this.length>1||N.test(d))&&M.test(a)&&(e=e.reverse());return this.pushStack(e,a,P.call(arguments).join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var V="abbr|article|aside|audio|canvas|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",W=/ jQuery\d+="(?:\d+|null)"/g,X=/^\s+/,Y=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,Z=/<([\w:]+)/,$=/",""],legend:[1,"
","
"],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"],col:[2,"","
"],area:[1,"",""],_default:[0,"",""]},bh=U(c);bg.optgroup=bg.option,bg.tbody=bg.tfoot=bg.colgroup=bg.caption=bg.thead,bg.th=bg.td,f.support.htmlSerialize||(bg._default=[1,"div
","
"]),f.fn.extend({text:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.text(a.call(this,b,c.text()))});if(typeof a!="object"&&a!==b)return this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a));return f.text(this)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=f.isFunction(a);return this.each(function(c){f(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f.clean(arguments);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f.clean(arguments));return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function() -{for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){if(a===b)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(W,""):null;if(typeof a=="string"&&!ba.test(a)&&(f.support.leadingWhitespace||!X.test(a))&&!bg[(Z.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Y,"<$1>");try{for(var c=0,d=this.length;c1&&l0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d,e,g,h=f.support.html5Clone||!bc.test("<"+a.nodeName)?a.cloneNode(!0):bo(a);if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bk(a,h),d=bl(a),e=bl(h);for(g=0;d[g];++g)e[g]&&bk(d[g],e[g])}if(b){bj(a,h);if(c){d=bl(a),e=bl(h);for(g=0;d[g];++g)bj(d[g],e[g])}}d=e=null;return h},clean:function(a,b,d,e){var g;b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);var h=[],i;for(var j=0,k;(k=a[j])!=null;j++){typeof k=="number"&&(k+="");if(!k)continue;if(typeof k=="string")if(!_.test(k))k=b.createTextNode(k);else{k=k.replace(Y,"<$1>");var l=(Z.exec(k)||["",""])[1].toLowerCase(),m=bg[l]||bg._default,n=m[0],o=b.createElement("div");b===c?bh.appendChild(o):U(b).appendChild(o),o.innerHTML=m[1]+k+m[2];while(n--)o=o.lastChild;if(!f.support.tbody){var p=$.test(k),q=l==="table"&&!p?o.firstChild&&o.firstChild.childNodes:m[1]===""&&!p?o.childNodes:[];for(i=q.length-1;i>=0;--i)f.nodeName(q[i],"tbody")&&!q[i].childNodes.length&&q[i].parentNode.removeChild(q[i])}!f.support.leadingWhitespace&&X.test(k)&&o.insertBefore(b.createTextNode(X.exec(k)[0]),o.firstChild),k=o.childNodes}var r;if(!f.support.appendChecked)if(k[0]&&typeof (r=k.length)=="number")for(i=0;i=0)return b+"px"}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return br.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=f.isNumeric(b)?"alpha(opacity="+b*100+")":"",g=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&f.trim(g.replace(bq,""))===""){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bq.test(g)?g.replace(bq,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){var c;f.swap(a,{display:"inline-block"},function(){b?c=bz(a,"margin-right","marginRight"):c=a.style.marginRight});return c}})}),c.defaultView&&c.defaultView.getComputedStyle&&(bA=function(a,b){var c,d,e;b=b.replace(bs,"-$1").toLowerCase(),(d=a.ownerDocument.defaultView)&&(e=d.getComputedStyle(a,null))&&(c=e.getPropertyValue(b),c===""&&!f.contains(a.ownerDocument.documentElement,a)&&(c=f.style(a,b)));return c}),c.documentElement.currentStyle&&(bB=function(a,b){var c,d,e,f=a.currentStyle&&a.currentStyle[b],g=a.style;f===null&&g&&(e=g[b])&&(f=e),!bt.test(f)&&bu.test(f)&&(c=g.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),g.left=b==="fontSize"?"1em":f||0,f=g.pixelLeft+"px",g.left=c,d&&(a.runtimeStyle.left=d));return f===""?"auto":f}),bz=bA||bB,f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style&&a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)});var bD=/%20/g,bE=/\[\]$/,bF=/\r?\n/g,bG=/#.*$/,bH=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bI=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bJ=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,bK=/^(?:GET|HEAD)$/,bL=/^\/\//,bM=/\?/,bN=/)<[^<]*)*<\/script>/gi,bO=/^(?:select|textarea)/i,bP=/\s+/,bQ=/([?&])_=[^&]*/,bR=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bS=f.fn.load,bT={},bU={},bV,bW,bX=["*/"]+["*"];try{bV=e.href}catch(bY){bV=c.createElement("a"),bV.href="",bV=bV.href}bW=bR.exec(bV.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bS)return bS.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("
").append(c.replace(bN,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bO.test(this.nodeName)||bI.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bF,"\r\n")}}):{name:b.name,value:c.replace(bF,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.on(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?b_(a,f.ajaxSettings):(b=a,a=f.ajaxSettings),b_(a,b);return a},ajaxSettings:{url:bV,isLocal:bJ.test(bW[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":bX},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:bZ(bT),ajaxTransport:bZ(bU),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a>0?4:0;var o,r,u,w=c,x=l?cb(d,v,l):b,y,z;if(a>=200&&a<300||a===304){if(d.ifModified){if(y=v.getResponseHeader("Last-Modified"))f.lastModified[k]=y;if(z=v.getResponseHeader("Etag"))f.etag[k]=z}if(a===304)w="notmodified",o=!0;else try{r=cc(d,x),w="success",o=!0}catch(A){w="parsererror",u=A}}else{u=w;if(!w||a)w="error",a<0&&(a=0)}v.status=a,v.statusText=""+(c||w),o?h.resolveWith(e,[r,w,v]):h.rejectWith(e,[v,w,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.fireWith(e,[v,w]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f.Callbacks("once memory"),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bH.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.add,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bG,"").replace(bL,bW[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bP),d.crossDomain==null&&(r=bR.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bW[1]&&r[2]==bW[2]&&(r[3]||(r[1]==="http:"?80:443))==(bW[3]||(bW[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),b$(bT,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bK.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bM.test(d.url)?"&":"?")+d.data,delete d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bQ,"$1_="+x);d.url=y+(y===d.url?(bM.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", "+bX+"; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=b$(bU,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){if(s<2)w(-1,z);else throw z}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)ca(g,a[g],c,e);return d.join("&").replace(bD,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var cd=f.now(),ce=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+cd++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=b.contentType==="application/x-www-form-urlencoded"&&typeof b.data=="string";if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(ce.test(b.url)||e&&ce.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(ce,l),b.url===j&&(e&&(k=k.replace(ce,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var cf=a.ActiveXObject?function(){for(var a in ch)ch[a](0,1)}:!1,cg=0,ch;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ci()||cj()}:ci,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,cf&&delete ch[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n),m.text=h.responseText;try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cg,cf&&(ch||(ch={},f(a).unload(cf)),ch[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var ck={},cl,cm,cn=/^(?:toggle|show|hide)$/,co=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,cp,cq=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cr;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(cu("show",3),a,b,c);for(var g=0,h=this.length;g=i.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),i.animatedProperties[this.prop]=!0;for(b in i.animatedProperties)i.animatedProperties[b]!==!0&&(g=!1);if(g){i.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){h.style["overflow"+b]=i.overflow[a]}),i.hide&&f(h).hide();if(i.hide||i.show)for(b in i.animatedProperties)f.style(h,b,i.orig[b]),f.removeData(h,"fxshow"+b,!0),f.removeData(h,"toggle"+b,!0);d=i.complete,d&&(i.complete=!1,d.call(h))}return!1}i.duration==Infinity?this.now=e:(c=e-this.startTime,this.state=c/i.duration,this.pos=f.easing[i.animatedProperties[this.prop]](this.state,c,0,1,i.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){var a,b=f.timers,c=0;for(;c-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cx.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cx.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each(["Left","Top"],function(a,c){var d="scroll"+c;f.fn[d]=function(c){var e,g;if(c===b){e=this[0];if(!e)return null;g=cy(e);return g?"pageXOffset"in g?g[a?"pageYOffset":"pageXOffset"]:f.support.boxModel&&g.document.documentElement[d]||g.document.body[d]:e[d]}return this.each(function(){g=cy(this),g?g.scrollTo(a?f(g).scrollLeft():c,a?c:f(g).scrollTop()):this[d]=c})}}),f.each(["Height","Width"],function(a,c){var d=c.toLowerCase();f.fn["inner"+c]=function(){var a=this[0];return a?a.style?parseFloat(f.css(a,d,"padding")):this[d]():null},f.fn["outer"+c]=function(a){var b=this[0];return b?b.style?parseFloat(f.css(b,d,a?"margin":"border")):this[d]():null},f.fn[d]=function(a){var e=this[0];if(!e)return a==null?null:this;if(f.isFunction(a))return this.each(function(b){var c=f(this);c[d](a.call(this,b,c[d]()))});if(f.isWindow(e)){var g=e.document.documentElement["client"+c],h=e.document.body;return e.document.compatMode==="CSS1Compat"&&g||h&&h["client"+c]||g}if(e.nodeType===9)return Math.max(e.documentElement["client"+c],e.body["scroll"+c],e.documentElement["scroll"+c],e.body["offset"+c],e.documentElement["offset"+c]);if(a===b){var i=f.css(e,d),j=parseFloat(i);return f.isNumeric(j)?j:i}return this.css(d,typeof a=="string"?a:a+"px")}}),a.jQuery=a.$=f,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return f})})(window); \ No newline at end of file diff --git a/test/lib/browser/mocha.css b/test/lib/browser/mocha.css deleted file mode 100755 index 01bac1abc18..00000000000 --- a/test/lib/browser/mocha.css +++ /dev/null @@ -1,199 +0,0 @@ -@charset "UTF-8"; -body { - font: 20px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif; - padding: 60px 50px; -} - -#mocha ul, #mocha li { - margin: 0; - padding: 0; -} - -#mocha ul { - list-style: none; -} - -#mocha h1, #mocha h2 { - margin: 0; -} - -#mocha h1 { - margin-top: 15px; - font-size: 1em; - font-weight: 200; -} - -#mocha h1 a { - text-decoration: none; - color: inherit; -} - -#mocha h1 a:hover { - text-decoration: underline; -} - -#mocha .suite .suite h1 { - margin-top: 0; - font-size: .8em; -} - -#mocha h2 { - font-size: 12px; - font-weight: normal; - cursor: pointer; -} - -#mocha .suite { - margin-left: 15px; -} - -#mocha .test { - margin-left: 15px; -} - -#mocha .test:hover h2::after { - position: relative; - top: 0; - right: -10px; - content: '(view source)'; - font-size: 12px; - font-family: arial; - color: #888; -} - -#mocha .test.pending:hover h2::after { - content: '(pending)'; - font-family: arial; -} - -#mocha .test.pass.medium .duration { - background: #C09853; -} - -#mocha .test.pass.slow .duration { - background: #B94A48; -} - -#mocha .test.pass::before { - content: '✓'; - font-size: 12px; - display: block; - float: left; - margin-right: 5px; - color: #00d6b2; -} - -#mocha .test.pass .duration { - font-size: 9px; - margin-left: 5px; - padding: 2px 5px; - color: white; - -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); - -moz-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); - box-shadow: inset 0 1px 1px rgba(0,0,0,.2); - -webkit-border-radius: 5px; - -moz-border-radius: 5px; - -ms-border-radius: 5px; - -o-border-radius: 5px; - border-radius: 5px; -} - -#mocha .test.pass.fast .duration { - display: none; -} - -#mocha .test.pending { - color: #0b97c4; -} - -#mocha .test.pending::before { - content: '◦'; - color: #0b97c4; -} - -#mocha .test.fail { - color: #c00; -} - -#mocha .test.fail pre { - color: black; -} - -#mocha .test.fail::before { - content: '✖'; - font-size: 12px; - display: block; - float: left; - margin-right: 5px; - color: #c00; -} - -#mocha .test pre.error { - color: #c00; -} - -#mocha .test pre { - display: inline-block; - font: 12px/1.5 monaco, monospace; - margin: 5px; - padding: 15px; - border: 1px solid #eee; - border-bottom-color: #ddd; - -webkit-border-radius: 3px; - -webkit-box-shadow: 0 1px 3px #eee; -} - -#report.pass .test.fail { - display: none; -} - -#report.fail .test.pass { - display: none; -} - -#error { - color: #c00; - font-size: 1.5 em; - font-weight: 100; - letter-spacing: 1px; -} - -#stats { - position: fixed; - top: 15px; - right: 10px; - font-size: 12px; - margin: 0; - color: #888; -} - -#stats .progress { - float: right; - padding-top: 0; -} - -#stats em { - color: black; -} - -#stats a { - text-decoration: none; - color: inherit; -} - -#stats a:hover { - border-bottom: 1px solid #eee; -} - -#stats li { - display: inline-block; - margin: 0 5px; - list-style: none; - padding-top: 11px; -} - -code .comment { color: #ddd } -code .init { color: #2F6FAD } -code .string { color: #5890AD } -code .keyword { color: #8A6343 } -code .number { color: #2F6FAD } diff --git a/test/lib/chai.js b/test/lib/chai.js deleted file mode 100644 index 7c9017149ee..00000000000 --- a/test/lib/chai.js +++ /dev/null @@ -1,5332 +0,0 @@ - -;(function(){ - -/** - * Require the module at `name`. - * - * @param {String} name - * @return {Object} exports - * @api public - */ - -function require(name) { - var module = require.modules[name]; - if (!module) throw new Error('failed to require "' + name + '"'); - - if (!('exports' in module) && typeof module.definition === 'function') { - module.client = module.component = true; - module.definition.call(this, module.exports = {}, module); - delete module.definition; - } - - return module.exports; -} - -/** - * Meta info, accessible in the global scope unless you use AMD option. - */ - -require.loader = 'component'; - -/** - * Internal helper object, contains a sorting function for semantiv versioning - */ -require.helper = {}; -require.helper.semVerSort = function(a, b) { - var aArray = a.version.split('.'); - var bArray = b.version.split('.'); - for (var i=0; i bLex ? 1 : -1; - continue; - } else if (aInt > bInt) { - return 1; - } else { - return -1; - } - } - return 0; -} - -/** - * Find and require a module which name starts with the provided name. - * If multiple modules exists, the highest semver is used. - * This function can only be used for remote dependencies. - - * @param {String} name - module name: `user~repo` - * @param {Boolean} returnPath - returns the canonical require path if true, - * otherwise it returns the epxorted module - */ -require.latest = function (name, returnPath) { - function showError(name) { - throw new Error('failed to find latest module of "' + name + '"'); - } - // only remotes with semvers, ignore local files conataining a '/' - var versionRegexp = /(.*)~(.*)@v?(\d+\.\d+\.\d+[^\/]*)$/; - var remoteRegexp = /(.*)~(.*)/; - if (!remoteRegexp.test(name)) showError(name); - var moduleNames = Object.keys(require.modules); - var semVerCandidates = []; - var otherCandidates = []; // for instance: name of the git branch - for (var i=0; i 0) { - var module = semVerCandidates.sort(require.helper.semVerSort).pop().name; - if (returnPath === true) { - return module; - } - return require(module); - } - // if the build contains more than one branch of the same module - // you should not use this funciton - var module = otherCandidates.sort(function(a, b) {return a.name > b.name})[0].name; - if (returnPath === true) { - return module; - } - return require(module); -} - -/** - * Registered modules. - */ - -require.modules = {}; - -/** - * Register module at `name` with callback `definition`. - * - * @param {String} name - * @param {Function} definition - * @api private - */ - -require.register = function (name, definition) { - require.modules[name] = { - definition: definition - }; -}; - -/** - * Define a module's exports immediately with `exports`. - * - * @param {String} name - * @param {Generic} exports - * @api private - */ - -require.define = function (name, exports) { - require.modules[name] = { - exports: exports - }; -}; -require.register("chaijs~assertion-error@1.0.0", function (exports, module) { -/*! - * assertion-error - * Copyright(c) 2013 Jake Luer - * MIT Licensed - */ - -/*! - * Return a function that will copy properties from - * one object to another excluding any originally - * listed. Returned function will create a new `{}`. - * - * @param {String} excluded properties ... - * @return {Function} - */ - -function exclude () { - var excludes = [].slice.call(arguments); - - function excludeProps (res, obj) { - Object.keys(obj).forEach(function (key) { - if (!~excludes.indexOf(key)) res[key] = obj[key]; - }); - } - - return function extendExclude () { - var args = [].slice.call(arguments) - , i = 0 - , res = {}; - - for (; i < args.length; i++) { - excludeProps(res, args[i]); - } - - return res; - }; -}; - -/*! - * Primary Exports - */ - -module.exports = AssertionError; - -/** - * ### AssertionError - * - * An extension of the JavaScript `Error` constructor for - * assertion and validation scenarios. - * - * @param {String} message - * @param {Object} properties to include (optional) - * @param {callee} start stack function (optional) - */ - -function AssertionError (message, _props, ssf) { - var extend = exclude('name', 'message', 'stack', 'constructor', 'toJSON') - , props = extend(_props || {}); - - // default values - this.message = message || 'Unspecified AssertionError'; - this.showDiff = false; - - // copy from properties - for (var key in props) { - this[key] = props[key]; - } - - // capture stack trace - ssf = ssf || arguments.callee; - if (ssf && Error.captureStackTrace) { - Error.captureStackTrace(this, ssf); - } -} - -/*! - * Inherit from Error.prototype - */ - -AssertionError.prototype = Object.create(Error.prototype); - -/*! - * Statically set name - */ - -AssertionError.prototype.name = 'AssertionError'; - -/*! - * Ensure correct constructor - */ - -AssertionError.prototype.constructor = AssertionError; - -/** - * Allow errors to be converted to JSON for static transfer. - * - * @param {Boolean} include stack (default: `true`) - * @return {Object} object that can be `JSON.stringify` - */ - -AssertionError.prototype.toJSON = function (stack) { - var extend = exclude('constructor', 'toJSON', 'stack') - , props = extend({ name: this.name }, this); - - // include stack if exists and not turned off - if (false !== stack && this.stack) { - props.stack = this.stack; - } - - return props; -}; - -}); - -require.register("chaijs~type-detect@0.1.1", function (exports, module) { -/*! - * type-detect - * Copyright(c) 2013 jake luer - * MIT Licensed - */ - -/*! - * Primary Exports - */ - -var exports = module.exports = getType; - -/*! - * Detectable javascript natives - */ - -var natives = { - '[object Array]': 'array' - , '[object RegExp]': 'regexp' - , '[object Function]': 'function' - , '[object Arguments]': 'arguments' - , '[object Date]': 'date' -}; - -/** - * ### typeOf (obj) - * - * Use several different techniques to determine - * the type of object being tested. - * - * - * @param {Mixed} object - * @return {String} object type - * @api public - */ - -function getType (obj) { - var str = Object.prototype.toString.call(obj); - if (natives[str]) return natives[str]; - if (obj === null) return 'null'; - if (obj === undefined) return 'undefined'; - if (obj === Object(obj)) return 'object'; - return typeof obj; -} - -exports.Library = Library; - -/** - * ### Library - * - * Create a repository for custom type detection. - * - * ```js - * var lib = new type.Library; - * ``` - * - */ - -function Library () { - this.tests = {}; -} - -/** - * #### .of (obj) - * - * Expose replacement `typeof` detection to the library. - * - * ```js - * if ('string' === lib.of('hello world')) { - * // ... - * } - * ``` - * - * @param {Mixed} object to test - * @return {String} type - */ - -Library.prototype.of = getType; - -/** - * #### .define (type, test) - * - * Add a test to for the `.test()` assertion. - * - * Can be defined as a regular expression: - * - * ```js - * lib.define('int', /^[0-9]+$/); - * ``` - * - * ... or as a function: - * - * ```js - * lib.define('bln', function (obj) { - * if ('boolean' === lib.of(obj)) return true; - * var blns = [ 'yes', 'no', 'true', 'false', 1, 0 ]; - * if ('string' === lib.of(obj)) obj = obj.toLowerCase(); - * return !! ~blns.indexOf(obj); - * }); - * ``` - * - * @param {String} type - * @param {RegExp|Function} test - * @api public - */ - -Library.prototype.define = function (type, test) { - if (arguments.length === 1) return this.tests[type]; - this.tests[type] = test; - return this; -}; - -/** - * #### .test (obj, test) - * - * Assert that an object is of type. Will first - * check natives, and if that does not pass it will - * use the user defined custom tests. - * - * ```js - * assert(lib.test('1', 'int')); - * assert(lib.test('yes', 'bln')); - * ``` - * - * @param {Mixed} object - * @param {String} type - * @return {Boolean} result - * @api public - */ - -Library.prototype.test = function (obj, type) { - if (type === getType(obj)) return true; - var test = this.tests[type]; - - if (test && 'regexp' === getType(test)) { - return test.test(obj); - } else if (test && 'function' === getType(test)) { - return test(obj); - } else { - throw new ReferenceError('Type test "' + type + '" not defined or invalid.'); - } -}; - -}); - -require.register("chaijs~deep-eql@0.1.3", function (exports, module) { -/*! - * deep-eql - * Copyright(c) 2013 Jake Luer - * MIT Licensed - */ - -/*! - * Module dependencies - */ - -var type = require('chaijs~type-detect@0.1.1'); - -/*! - * Buffer.isBuffer browser shim - */ - -var Buffer; -try { Buffer = require('buffer').Buffer; } -catch(ex) { - Buffer = {}; - Buffer.isBuffer = function() { return false; } -} - -/*! - * Primary Export - */ - -module.exports = deepEqual; - -/** - * Assert super-strict (egal) equality between - * two objects of any type. - * - * @param {Mixed} a - * @param {Mixed} b - * @param {Array} memoised (optional) - * @return {Boolean} equal match - */ - -function deepEqual(a, b, m) { - if (sameValue(a, b)) { - return true; - } else if ('date' === type(a)) { - return dateEqual(a, b); - } else if ('regexp' === type(a)) { - return regexpEqual(a, b); - } else if (Buffer.isBuffer(a)) { - return bufferEqual(a, b); - } else if ('arguments' === type(a)) { - return argumentsEqual(a, b, m); - } else if (!typeEqual(a, b)) { - return false; - } else if (('object' !== type(a) && 'object' !== type(b)) - && ('array' !== type(a) && 'array' !== type(b))) { - return sameValue(a, b); - } else { - return objectEqual(a, b, m); - } -} - -/*! - * Strict (egal) equality test. Ensures that NaN always - * equals NaN and `-0` does not equal `+0`. - * - * @param {Mixed} a - * @param {Mixed} b - * @return {Boolean} equal match - */ - -function sameValue(a, b) { - if (a === b) return a !== 0 || 1 / a === 1 / b; - return a !== a && b !== b; -} - -/*! - * Compare the types of two given objects and - * return if they are equal. Note that an Array - * has a type of `array` (not `object`) and arguments - * have a type of `arguments` (not `array`/`object`). - * - * @param {Mixed} a - * @param {Mixed} b - * @return {Boolean} result - */ - -function typeEqual(a, b) { - return type(a) === type(b); -} - -/*! - * Compare two Date objects by asserting that - * the time values are equal using `saveValue`. - * - * @param {Date} a - * @param {Date} b - * @return {Boolean} result - */ - -function dateEqual(a, b) { - if ('date' !== type(b)) return false; - return sameValue(a.getTime(), b.getTime()); -} - -/*! - * Compare two regular expressions by converting them - * to string and checking for `sameValue`. - * - * @param {RegExp} a - * @param {RegExp} b - * @return {Boolean} result - */ - -function regexpEqual(a, b) { - if ('regexp' !== type(b)) return false; - return sameValue(a.toString(), b.toString()); -} - -/*! - * Assert deep equality of two `arguments` objects. - * Unfortunately, these must be sliced to arrays - * prior to test to ensure no bad behavior. - * - * @param {Arguments} a - * @param {Arguments} b - * @param {Array} memoize (optional) - * @return {Boolean} result - */ - -function argumentsEqual(a, b, m) { - if ('arguments' !== type(b)) return false; - a = [].slice.call(a); - b = [].slice.call(b); - return deepEqual(a, b, m); -} - -/*! - * Get enumerable properties of a given object. - * - * @param {Object} a - * @return {Array} property names - */ - -function enumerable(a) { - var res = []; - for (var key in a) res.push(key); - return res; -} - -/*! - * Simple equality for flat iterable objects - * such as Arrays or Node.js buffers. - * - * @param {Iterable} a - * @param {Iterable} b - * @return {Boolean} result - */ - -function iterableEqual(a, b) { - if (a.length !== b.length) return false; - - var i = 0; - var match = true; - - for (; i < a.length; i++) { - if (a[i] !== b[i]) { - match = false; - break; - } - } - - return match; -} - -/*! - * Extension to `iterableEqual` specifically - * for Node.js Buffers. - * - * @param {Buffer} a - * @param {Mixed} b - * @return {Boolean} result - */ - -function bufferEqual(a, b) { - if (!Buffer.isBuffer(b)) return false; - return iterableEqual(a, b); -} - -/*! - * Block for `objectEqual` ensuring non-existing - * values don't get in. - * - * @param {Mixed} object - * @return {Boolean} result - */ - -function isValue(a) { - return a !== null && a !== undefined; -} - -/*! - * Recursively check the equality of two objects. - * Once basic sameness has been established it will - * defer to `deepEqual` for each enumerable key - * in the object. - * - * @param {Mixed} a - * @param {Mixed} b - * @return {Boolean} result - */ - -function objectEqual(a, b, m) { - if (!isValue(a) || !isValue(b)) { - return false; - } - - if (a.prototype !== b.prototype) { - return false; - } - - var i; - if (m) { - for (i = 0; i < m.length; i++) { - if ((m[i][0] === a && m[i][1] === b) - || (m[i][0] === b && m[i][1] === a)) { - return true; - } - } - } else { - m = []; - } - - try { - var ka = enumerable(a); - var kb = enumerable(b); - } catch (ex) { - return false; - } - - ka.sort(); - kb.sort(); - - if (!iterableEqual(ka, kb)) { - return false; - } - - m.push([ a, b ]); - - var key; - for (i = ka.length - 1; i >= 0; i--) { - key = ka[i]; - if (!deepEqual(a[key], b[key], m)) { - return false; - } - } - - return true; -} - -}); - -require.register("chai", function (exports, module) { -module.exports = require('chai/lib/chai.js'); - -}); - -require.register("chai/lib/chai.js", function (exports, module) { -/*! - * chai - * Copyright(c) 2011-2014 Jake Luer - * MIT Licensed - */ - -var used = [] - , exports = module.exports = {}; - -/*! - * Chai version - */ - -exports.version = '2.1.0'; - -/*! - * Assertion Error - */ - -exports.AssertionError = require('chaijs~assertion-error@1.0.0'); - -/*! - * Utils for plugins (not exported) - */ - -var util = require('chai/lib/chai/utils/index.js'); - -/** - * # .use(function) - * - * Provides a way to extend the internals of Chai - * - * @param {Function} - * @returns {this} for chaining - * @api public - */ - -exports.use = function (fn) { - if (!~used.indexOf(fn)) { - fn(this, util); - used.push(fn); - } - - return this; -}; - -/*! - * Utility Functions - */ - -exports.util = util; - -/*! - * Configuration - */ - -var config = require('chai/lib/chai/config.js'); -exports.config = config; - -/*! - * Primary `Assertion` prototype - */ - -var assertion = require('chai/lib/chai/assertion.js'); -exports.use(assertion); - -/*! - * Core Assertions - */ - -var core = require('chai/lib/chai/core/assertions.js'); -exports.use(core); - -/*! - * Expect interface - */ - -var expect = require('chai/lib/chai/interface/expect.js'); -exports.use(expect); - -/*! - * Should interface - */ - -var should = require('chai/lib/chai/interface/should.js'); -exports.use(should); - -/*! - * Assert interface - */ - -var assert = require('chai/lib/chai/interface/assert.js'); -exports.use(assert); - -}); - -require.register("chai/lib/chai/assertion.js", function (exports, module) { -/*! - * chai - * http://chaijs.com - * Copyright(c) 2011-2014 Jake Luer - * MIT Licensed - */ - -var config = require('chai/lib/chai/config.js'); - -module.exports = function (_chai, util) { - /*! - * Module dependencies. - */ - - var AssertionError = _chai.AssertionError - , flag = util.flag; - - /*! - * Module export. - */ - - _chai.Assertion = Assertion; - - /*! - * Assertion Constructor - * - * Creates object for chaining. - * - * @api private - */ - - function Assertion (obj, msg, stack) { - flag(this, 'ssfi', stack || arguments.callee); - flag(this, 'object', obj); - flag(this, 'message', msg); - } - - Object.defineProperty(Assertion, 'includeStack', { - get: function() { - console.warn('Assertion.includeStack is deprecated, use chai.config.includeStack instead.'); - return config.includeStack; - }, - set: function(value) { - console.warn('Assertion.includeStack is deprecated, use chai.config.includeStack instead.'); - config.includeStack = value; - } - }); - - Object.defineProperty(Assertion, 'showDiff', { - get: function() { - console.warn('Assertion.showDiff is deprecated, use chai.config.showDiff instead.'); - return config.showDiff; - }, - set: function(value) { - console.warn('Assertion.showDiff is deprecated, use chai.config.showDiff instead.'); - config.showDiff = value; - } - }); - - Assertion.addProperty = function (name, fn) { - util.addProperty(this.prototype, name, fn); - }; - - Assertion.addMethod = function (name, fn) { - util.addMethod(this.prototype, name, fn); - }; - - Assertion.addChainableMethod = function (name, fn, chainingBehavior) { - util.addChainableMethod(this.prototype, name, fn, chainingBehavior); - }; - - Assertion.overwriteProperty = function (name, fn) { - util.overwriteProperty(this.prototype, name, fn); - }; - - Assertion.overwriteMethod = function (name, fn) { - util.overwriteMethod(this.prototype, name, fn); - }; - - Assertion.overwriteChainableMethod = function (name, fn, chainingBehavior) { - util.overwriteChainableMethod(this.prototype, name, fn, chainingBehavior); - }; - - /*! - * ### .assert(expression, message, negateMessage, expected, actual) - * - * Executes an expression and check expectations. Throws AssertionError for reporting if test doesn't pass. - * - * @name assert - * @param {Philosophical} expression to be tested - * @param {String or Function} message or function that returns message to display if fails - * @param {String or Function} negatedMessage or function that returns negatedMessage to display if negated expression fails - * @param {Mixed} expected value (remember to check for negation) - * @param {Mixed} actual (optional) will default to `this.obj` - * @api private - */ - - Assertion.prototype.assert = function (expr, msg, negateMsg, expected, _actual, showDiff) { - var ok = util.test(this, arguments); - if (true !== showDiff) showDiff = false; - if (true !== config.showDiff) showDiff = false; - - if (!ok) { - var msg = util.getMessage(this, arguments) - , actual = util.getActual(this, arguments); - throw new AssertionError(msg, { - actual: actual - , expected: expected - , showDiff: showDiff - }, (config.includeStack) ? this.assert : flag(this, 'ssfi')); - } - }; - - /*! - * ### ._obj - * - * Quick reference to stored `actual` value for plugin developers. - * - * @api private - */ - - Object.defineProperty(Assertion.prototype, '_obj', - { get: function () { - return flag(this, 'object'); - } - , set: function (val) { - flag(this, 'object', val); - } - }); -}; - -}); - -require.register("chai/lib/chai/config.js", function (exports, module) { -module.exports = { - - /** - * ### config.includeStack - * - * User configurable property, influences whether stack trace - * is included in Assertion error message. Default of false - * suppresses stack trace in the error message. - * - * chai.config.includeStack = true; // enable stack on error - * - * @param {Boolean} - * @api public - */ - - includeStack: false, - - /** - * ### config.showDiff - * - * User configurable property, influences whether or not - * the `showDiff` flag should be included in the thrown - * AssertionErrors. `false` will always be `false`; `true` - * will be true when the assertion has requested a diff - * be shown. - * - * @param {Boolean} - * @api public - */ - - showDiff: true, - - /** - * ### config.truncateThreshold - * - * User configurable property, sets length threshold for actual and - * expected values in assertion errors. If this threshold is exceeded, - * the value is truncated. - * - * Set it to zero if you want to disable truncating altogether. - * - * chai.config.truncateThreshold = 0; // disable truncating - * - * @param {Number} - * @api public - */ - - truncateThreshold: 40 - -}; - -}); - -require.register("chai/lib/chai/core/assertions.js", function (exports, module) { -/*! - * chai - * http://chaijs.com - * Copyright(c) 2011-2014 Jake Luer - * MIT Licensed - */ - -module.exports = function (chai, _) { - var Assertion = chai.Assertion - , toString = Object.prototype.toString - , flag = _.flag; - - /** - * ### Language Chains - * - * The following are provided as chainable getters to - * improve the readability of your assertions. They - * do not provide testing capabilities unless they - * have been overwritten by a plugin. - * - * **Chains** - * - * - to - * - be - * - been - * - is - * - that - * - which - * - and - * - has - * - have - * - with - * - at - * - of - * - same - * - * @name language chains - * @api public - */ - - [ 'to', 'be', 'been' - , 'is', 'and', 'has', 'have' - , 'with', 'that', 'which', 'at' - , 'of', 'same' ].forEach(function (chain) { - Assertion.addProperty(chain, function () { - return this; - }); - }); - - /** - * ### .not - * - * Negates any of assertions following in the chain. - * - * expect(foo).to.not.equal('bar'); - * expect(goodFn).to.not.throw(Error); - * expect({ foo: 'baz' }).to.have.property('foo') - * .and.not.equal('bar'); - * - * @name not - * @api public - */ - - Assertion.addProperty('not', function () { - flag(this, 'negate', true); - }); - - /** - * ### .deep - * - * Sets the `deep` flag, later used by the `equal` and - * `property` assertions. - * - * expect(foo).to.deep.equal({ bar: 'baz' }); - * expect({ foo: { bar: { baz: 'quux' } } }) - * .to.have.deep.property('foo.bar.baz', 'quux'); - * - * @name deep - * @api public - */ - - Assertion.addProperty('deep', function () { - flag(this, 'deep', true); - }); - - /** - * ### .any - * - * Sets the `any` flag, (opposite of the `all` flag) - * later used in the `keys` assertion. - * - * expect(foo).to.have.any.keys('bar', 'baz'); - * - * @name any - * @api public - */ - - Assertion.addProperty('any', function () { - flag(this, 'any', true); - flag(this, 'all', false) - }); - - - /** - * ### .all - * - * Sets the `all` flag (opposite of the `any` flag) - * later used by the `keys` assertion. - * - * expect(foo).to.have.all.keys('bar', 'baz'); - * - * @name all - * @api public - */ - - Assertion.addProperty('all', function () { - flag(this, 'all', true); - flag(this, 'any', false); - }); - - /** - * ### .a(type) - * - * The `a` and `an` assertions are aliases that can be - * used either as language chains or to assert a value's - * type. - * - * // typeof - * expect('test').to.be.a('string'); - * expect({ foo: 'bar' }).to.be.an('object'); - * expect(null).to.be.a('null'); - * expect(undefined).to.be.an('undefined'); - * - * // language chain - * expect(foo).to.be.an.instanceof(Foo); - * - * @name a - * @alias an - * @param {String} type - * @param {String} message _optional_ - * @api public - */ - - function an (type, msg) { - if (msg) flag(this, 'message', msg); - type = type.toLowerCase(); - var obj = flag(this, 'object') - , article = ~[ 'a', 'e', 'i', 'o', 'u' ].indexOf(type.charAt(0)) ? 'an ' : 'a '; - - this.assert( - type === _.type(obj) - , 'expected #{this} to be ' + article + type - , 'expected #{this} not to be ' + article + type - ); - } - - Assertion.addChainableMethod('an', an); - Assertion.addChainableMethod('a', an); - - /** - * ### .include(value) - * - * The `include` and `contain` assertions can be used as either property - * based language chains or as methods to assert the inclusion of an object - * in an array or a substring in a string. When used as language chains, - * they toggle the `contains` flag for the `keys` assertion. - * - * expect([1,2,3]).to.include(2); - * expect('foobar').to.contain('foo'); - * expect({ foo: 'bar', hello: 'universe' }).to.include.keys('foo'); - * - * @name include - * @alias contain - * @alias includes - * @alias contains - * @param {Object|String|Number} obj - * @param {String} message _optional_ - * @api public - */ - - function includeChainingBehavior () { - flag(this, 'contains', true); - } - - function include (val, msg) { - if (msg) flag(this, 'message', msg); - var obj = flag(this, 'object'); - var expected = false; - if (_.type(obj) === 'array' && _.type(val) === 'object') { - for (var i in obj) { - if (_.eql(obj[i], val)) { - expected = true; - break; - } - } - } else if (_.type(val) === 'object') { - if (!flag(this, 'negate')) { - for (var k in val) new Assertion(obj).property(k, val[k]); - return; - } - var subset = {}; - for (var k in val) subset[k] = obj[k]; - expected = _.eql(subset, val); - } else { - expected = obj && ~obj.indexOf(val); - } - this.assert( - expected - , 'expected #{this} to include ' + _.inspect(val) - , 'expected #{this} to not include ' + _.inspect(val)); - } - - Assertion.addChainableMethod('include', include, includeChainingBehavior); - Assertion.addChainableMethod('contain', include, includeChainingBehavior); - Assertion.addChainableMethod('contains', include, includeChainingBehavior); - Assertion.addChainableMethod('includes', include, includeChainingBehavior); - - /** - * ### .ok - * - * Asserts that the target is truthy. - * - * expect('everthing').to.be.ok; - * expect(1).to.be.ok; - * expect(false).to.not.be.ok; - * expect(undefined).to.not.be.ok; - * expect(null).to.not.be.ok; - * - * @name ok - * @api public - */ - - Assertion.addProperty('ok', function () { - this.assert( - flag(this, 'object') - , 'expected #{this} to be truthy' - , 'expected #{this} to be falsy'); - }); - - /** - * ### .true - * - * Asserts that the target is `true`. - * - * expect(true).to.be.true; - * expect(1).to.not.be.true; - * - * @name true - * @api public - */ - - Assertion.addProperty('true', function () { - this.assert( - true === flag(this, 'object') - , 'expected #{this} to be true' - , 'expected #{this} to be false' - , this.negate ? false : true - ); - }); - - /** - * ### .false - * - * Asserts that the target is `false`. - * - * expect(false).to.be.false; - * expect(0).to.not.be.false; - * - * @name false - * @api public - */ - - Assertion.addProperty('false', function () { - this.assert( - false === flag(this, 'object') - , 'expected #{this} to be false' - , 'expected #{this} to be true' - , this.negate ? true : false - ); - }); - - /** - * ### .null - * - * Asserts that the target is `null`. - * - * expect(null).to.be.null; - * expect(undefined).not.to.be.null; - * - * @name null - * @api public - */ - - Assertion.addProperty('null', function () { - this.assert( - null === flag(this, 'object') - , 'expected #{this} to be null' - , 'expected #{this} not to be null' - ); - }); - - /** - * ### .undefined - * - * Asserts that the target is `undefined`. - * - * expect(undefined).to.be.undefined; - * expect(null).to.not.be.undefined; - * - * @name undefined - * @api public - */ - - Assertion.addProperty('undefined', function () { - this.assert( - undefined === flag(this, 'object') - , 'expected #{this} to be undefined' - , 'expected #{this} not to be undefined' - ); - }); - - /** - * ### .exist - * - * Asserts that the target is neither `null` nor `undefined`. - * - * var foo = 'hi' - * , bar = null - * , baz; - * - * expect(foo).to.exist; - * expect(bar).to.not.exist; - * expect(baz).to.not.exist; - * - * @name exist - * @api public - */ - - Assertion.addProperty('exist', function () { - this.assert( - null != flag(this, 'object') - , 'expected #{this} to exist' - , 'expected #{this} to not exist' - ); - }); - - - /** - * ### .empty - * - * Asserts that the target's length is `0`. For arrays, it checks - * the `length` property. For objects, it gets the count of - * enumerable keys. - * - * expect([]).to.be.empty; - * expect('').to.be.empty; - * expect({}).to.be.empty; - * - * @name empty - * @api public - */ - - Assertion.addProperty('empty', function () { - var obj = flag(this, 'object') - , expected = obj; - - if (Array.isArray(obj) || 'string' === typeof object) { - expected = obj.length; - } else if (typeof obj === 'object') { - expected = Object.keys(obj).length; - } - - this.assert( - !expected - , 'expected #{this} to be empty' - , 'expected #{this} not to be empty' - ); - }); - - /** - * ### .arguments - * - * Asserts that the target is an arguments object. - * - * function test () { - * expect(arguments).to.be.arguments; - * } - * - * @name arguments - * @alias Arguments - * @api public - */ - - function checkArguments () { - var obj = flag(this, 'object') - , type = Object.prototype.toString.call(obj); - this.assert( - '[object Arguments]' === type - , 'expected #{this} to be arguments but got ' + type - , 'expected #{this} to not be arguments' - ); - } - - Assertion.addProperty('arguments', checkArguments); - Assertion.addProperty('Arguments', checkArguments); - - /** - * ### .equal(value) - * - * Asserts that the target is strictly equal (`===`) to `value`. - * Alternately, if the `deep` flag is set, asserts that - * the target is deeply equal to `value`. - * - * expect('hello').to.equal('hello'); - * expect(42).to.equal(42); - * expect(1).to.not.equal(true); - * expect({ foo: 'bar' }).to.not.equal({ foo: 'bar' }); - * expect({ foo: 'bar' }).to.deep.equal({ foo: 'bar' }); - * - * @name equal - * @alias equals - * @alias eq - * @alias deep.equal - * @param {Mixed} value - * @param {String} message _optional_ - * @api public - */ - - function assertEqual (val, msg) { - if (msg) flag(this, 'message', msg); - var obj = flag(this, 'object'); - if (flag(this, 'deep')) { - return this.eql(val); - } else { - this.assert( - val === obj - , 'expected #{this} to equal #{exp}' - , 'expected #{this} to not equal #{exp}' - , val - , this._obj - , true - ); - } - } - - Assertion.addMethod('equal', assertEqual); - Assertion.addMethod('equals', assertEqual); - Assertion.addMethod('eq', assertEqual); - - /** - * ### .eql(value) - * - * Asserts that the target is deeply equal to `value`. - * - * expect({ foo: 'bar' }).to.eql({ foo: 'bar' }); - * expect([ 1, 2, 3 ]).to.eql([ 1, 2, 3 ]); - * - * @name eql - * @alias eqls - * @param {Mixed} value - * @param {String} message _optional_ - * @api public - */ - - function assertEql(obj, msg) { - if (msg) flag(this, 'message', msg); - this.assert( - _.eql(obj, flag(this, 'object')) - , 'expected #{this} to deeply equal #{exp}' - , 'expected #{this} to not deeply equal #{exp}' - , obj - , this._obj - , true - ); - } - - Assertion.addMethod('eql', assertEql); - Assertion.addMethod('eqls', assertEql); - - /** - * ### .above(value) - * - * Asserts that the target is greater than `value`. - * - * expect(10).to.be.above(5); - * - * Can also be used in conjunction with `length` to - * assert a minimum length. The benefit being a - * more informative error message than if the length - * was supplied directly. - * - * expect('foo').to.have.length.above(2); - * expect([ 1, 2, 3 ]).to.have.length.above(2); - * - * @name above - * @alias gt - * @alias greaterThan - * @param {Number} value - * @param {String} message _optional_ - * @api public - */ - - function assertAbove (n, msg) { - if (msg) flag(this, 'message', msg); - var obj = flag(this, 'object'); - if (flag(this, 'doLength')) { - new Assertion(obj, msg).to.have.property('length'); - var len = obj.length; - this.assert( - len > n - , 'expected #{this} to have a length above #{exp} but got #{act}' - , 'expected #{this} to not have a length above #{exp}' - , n - , len - ); - } else { - this.assert( - obj > n - , 'expected #{this} to be above ' + n - , 'expected #{this} to be at most ' + n - ); - } - } - - Assertion.addMethod('above', assertAbove); - Assertion.addMethod('gt', assertAbove); - Assertion.addMethod('greaterThan', assertAbove); - - /** - * ### .least(value) - * - * Asserts that the target is greater than or equal to `value`. - * - * expect(10).to.be.at.least(10); - * - * Can also be used in conjunction with `length` to - * assert a minimum length. The benefit being a - * more informative error message than if the length - * was supplied directly. - * - * expect('foo').to.have.length.of.at.least(2); - * expect([ 1, 2, 3 ]).to.have.length.of.at.least(3); - * - * @name least - * @alias gte - * @param {Number} value - * @param {String} message _optional_ - * @api public - */ - - function assertLeast (n, msg) { - if (msg) flag(this, 'message', msg); - var obj = flag(this, 'object'); - if (flag(this, 'doLength')) { - new Assertion(obj, msg).to.have.property('length'); - var len = obj.length; - this.assert( - len >= n - , 'expected #{this} to have a length at least #{exp} but got #{act}' - , 'expected #{this} to have a length below #{exp}' - , n - , len - ); - } else { - this.assert( - obj >= n - , 'expected #{this} to be at least ' + n - , 'expected #{this} to be below ' + n - ); - } - } - - Assertion.addMethod('least', assertLeast); - Assertion.addMethod('gte', assertLeast); - - /** - * ### .below(value) - * - * Asserts that the target is less than `value`. - * - * expect(5).to.be.below(10); - * - * Can also be used in conjunction with `length` to - * assert a maximum length. The benefit being a - * more informative error message than if the length - * was supplied directly. - * - * expect('foo').to.have.length.below(4); - * expect([ 1, 2, 3 ]).to.have.length.below(4); - * - * @name below - * @alias lt - * @alias lessThan - * @param {Number} value - * @param {String} message _optional_ - * @api public - */ - - function assertBelow (n, msg) { - if (msg) flag(this, 'message', msg); - var obj = flag(this, 'object'); - if (flag(this, 'doLength')) { - new Assertion(obj, msg).to.have.property('length'); - var len = obj.length; - this.assert( - len < n - , 'expected #{this} to have a length below #{exp} but got #{act}' - , 'expected #{this} to not have a length below #{exp}' - , n - , len - ); - } else { - this.assert( - obj < n - , 'expected #{this} to be below ' + n - , 'expected #{this} to be at least ' + n - ); - } - } - - Assertion.addMethod('below', assertBelow); - Assertion.addMethod('lt', assertBelow); - Assertion.addMethod('lessThan', assertBelow); - - /** - * ### .most(value) - * - * Asserts that the target is less than or equal to `value`. - * - * expect(5).to.be.at.most(5); - * - * Can also be used in conjunction with `length` to - * assert a maximum length. The benefit being a - * more informative error message than if the length - * was supplied directly. - * - * expect('foo').to.have.length.of.at.most(4); - * expect([ 1, 2, 3 ]).to.have.length.of.at.most(3); - * - * @name most - * @alias lte - * @param {Number} value - * @param {String} message _optional_ - * @api public - */ - - function assertMost (n, msg) { - if (msg) flag(this, 'message', msg); - var obj = flag(this, 'object'); - if (flag(this, 'doLength')) { - new Assertion(obj, msg).to.have.property('length'); - var len = obj.length; - this.assert( - len <= n - , 'expected #{this} to have a length at most #{exp} but got #{act}' - , 'expected #{this} to have a length above #{exp}' - , n - , len - ); - } else { - this.assert( - obj <= n - , 'expected #{this} to be at most ' + n - , 'expected #{this} to be above ' + n - ); - } - } - - Assertion.addMethod('most', assertMost); - Assertion.addMethod('lte', assertMost); - - /** - * ### .within(start, finish) - * - * Asserts that the target is within a range. - * - * expect(7).to.be.within(5,10); - * - * Can also be used in conjunction with `length` to - * assert a length range. The benefit being a - * more informative error message than if the length - * was supplied directly. - * - * expect('foo').to.have.length.within(2,4); - * expect([ 1, 2, 3 ]).to.have.length.within(2,4); - * - * @name within - * @param {Number} start lowerbound inclusive - * @param {Number} finish upperbound inclusive - * @param {String} message _optional_ - * @api public - */ - - Assertion.addMethod('within', function (start, finish, msg) { - if (msg) flag(this, 'message', msg); - var obj = flag(this, 'object') - , range = start + '..' + finish; - if (flag(this, 'doLength')) { - new Assertion(obj, msg).to.have.property('length'); - var len = obj.length; - this.assert( - len >= start && len <= finish - , 'expected #{this} to have a length within ' + range - , 'expected #{this} to not have a length within ' + range - ); - } else { - this.assert( - obj >= start && obj <= finish - , 'expected #{this} to be within ' + range - , 'expected #{this} to not be within ' + range - ); - } - }); - - /** - * ### .instanceof(constructor) - * - * Asserts that the target is an instance of `constructor`. - * - * var Tea = function (name) { this.name = name; } - * , Chai = new Tea('chai'); - * - * expect(Chai).to.be.an.instanceof(Tea); - * expect([ 1, 2, 3 ]).to.be.instanceof(Array); - * - * @name instanceof - * @param {Constructor} constructor - * @param {String} message _optional_ - * @alias instanceOf - * @api public - */ - - function assertInstanceOf (constructor, msg) { - if (msg) flag(this, 'message', msg); - var name = _.getName(constructor); - this.assert( - flag(this, 'object') instanceof constructor - , 'expected #{this} to be an instance of ' + name - , 'expected #{this} to not be an instance of ' + name - ); - }; - - Assertion.addMethod('instanceof', assertInstanceOf); - Assertion.addMethod('instanceOf', assertInstanceOf); - - /** - * ### .property(name, [value]) - * - * Asserts that the target has a property `name`, optionally asserting that - * the value of that property is strictly equal to `value`. - * If the `deep` flag is set, you can use dot- and bracket-notation for deep - * references into objects and arrays. - * - * // simple referencing - * var obj = { foo: 'bar' }; - * expect(obj).to.have.property('foo'); - * expect(obj).to.have.property('foo', 'bar'); - * - * // deep referencing - * var deepObj = { - * green: { tea: 'matcha' } - * , teas: [ 'chai', 'matcha', { tea: 'konacha' } ] - * }; - - * expect(deepObj).to.have.deep.property('green.tea', 'matcha'); - * expect(deepObj).to.have.deep.property('teas[1]', 'matcha'); - * expect(deepObj).to.have.deep.property('teas[2].tea', 'konacha'); - * - * You can also use an array as the starting point of a `deep.property` - * assertion, or traverse nested arrays. - * - * var arr = [ - * [ 'chai', 'matcha', 'konacha' ] - * , [ { tea: 'chai' } - * , { tea: 'matcha' } - * , { tea: 'konacha' } ] - * ]; - * - * expect(arr).to.have.deep.property('[0][1]', 'matcha'); - * expect(arr).to.have.deep.property('[1][2].tea', 'konacha'); - * - * Furthermore, `property` changes the subject of the assertion - * to be the value of that property from the original object. This - * permits for further chainable assertions on that property. - * - * expect(obj).to.have.property('foo') - * .that.is.a('string'); - * expect(deepObj).to.have.property('green') - * .that.is.an('object') - * .that.deep.equals({ tea: 'matcha' }); - * expect(deepObj).to.have.property('teas') - * .that.is.an('array') - * .with.deep.property('[2]') - * .that.deep.equals({ tea: 'konacha' }); - * - * @name property - * @alias deep.property - * @param {String} name - * @param {Mixed} value (optional) - * @param {String} message _optional_ - * @returns value of property for chaining - * @api public - */ - - Assertion.addMethod('property', function (name, val, msg) { - if (msg) flag(this, 'message', msg); - - var isDeep = !!flag(this, 'deep') - , descriptor = isDeep ? 'deep property ' : 'property ' - , negate = flag(this, 'negate') - , obj = flag(this, 'object') - , pathInfo = isDeep ? _.getPathInfo(name, obj) : null - , hasProperty = isDeep - ? pathInfo.exists - : _.hasProperty(name, obj) - , value = isDeep - ? pathInfo.value - : obj[name]; - - if (negate && undefined !== val) { - if (undefined === value) { - msg = (msg != null) ? msg + ': ' : ''; - throw new Error(msg + _.inspect(obj) + ' has no ' + descriptor + _.inspect(name)); - } - } else { - this.assert( - hasProperty - , 'expected #{this} to have a ' + descriptor + _.inspect(name) - , 'expected #{this} to not have ' + descriptor + _.inspect(name)); - } - - if (undefined !== val) { - this.assert( - val === value - , 'expected #{this} to have a ' + descriptor + _.inspect(name) + ' of #{exp}, but got #{act}' - , 'expected #{this} to not have a ' + descriptor + _.inspect(name) + ' of #{act}' - , val - , value - ); - } - - flag(this, 'object', value); - }); - - - /** - * ### .ownProperty(name) - * - * Asserts that the target has an own property `name`. - * - * expect('test').to.have.ownProperty('length'); - * - * @name ownProperty - * @alias haveOwnProperty - * @param {String} name - * @param {String} message _optional_ - * @api public - */ - - function assertOwnProperty (name, msg) { - if (msg) flag(this, 'message', msg); - var obj = flag(this, 'object'); - this.assert( - obj.hasOwnProperty(name) - , 'expected #{this} to have own property ' + _.inspect(name) - , 'expected #{this} to not have own property ' + _.inspect(name) - ); - } - - Assertion.addMethod('ownProperty', assertOwnProperty); - Assertion.addMethod('haveOwnProperty', assertOwnProperty); - - /** - * ### .length(value) - * - * Asserts that the target's `length` property has - * the expected value. - * - * expect([ 1, 2, 3]).to.have.length(3); - * expect('foobar').to.have.length(6); - * - * Can also be used as a chain precursor to a value - * comparison for the length property. - * - * expect('foo').to.have.length.above(2); - * expect([ 1, 2, 3 ]).to.have.length.above(2); - * expect('foo').to.have.length.below(4); - * expect([ 1, 2, 3 ]).to.have.length.below(4); - * expect('foo').to.have.length.within(2,4); - * expect([ 1, 2, 3 ]).to.have.length.within(2,4); - * - * @name length - * @alias lengthOf - * @param {Number} length - * @param {String} message _optional_ - * @api public - */ - - function assertLengthChain () { - flag(this, 'doLength', true); - } - - function assertLength (n, msg) { - if (msg) flag(this, 'message', msg); - var obj = flag(this, 'object'); - new Assertion(obj, msg).to.have.property('length'); - var len = obj.length; - - this.assert( - len == n - , 'expected #{this} to have a length of #{exp} but got #{act}' - , 'expected #{this} to not have a length of #{act}' - , n - , len - ); - } - - Assertion.addChainableMethod('length', assertLength, assertLengthChain); - Assertion.addMethod('lengthOf', assertLength); - - /** - * ### .match(regexp) - * - * Asserts that the target matches a regular expression. - * - * expect('foobar').to.match(/^foo/); - * - * @name match - * @param {RegExp} RegularExpression - * @param {String} message _optional_ - * @api public - */ - - Assertion.addMethod('match', function (re, msg) { - if (msg) flag(this, 'message', msg); - var obj = flag(this, 'object'); - this.assert( - re.exec(obj) - , 'expected #{this} to match ' + re - , 'expected #{this} not to match ' + re - ); - }); - - /** - * ### .string(string) - * - * Asserts that the string target contains another string. - * - * expect('foobar').to.have.string('bar'); - * - * @name string - * @param {String} string - * @param {String} message _optional_ - * @api public - */ - - Assertion.addMethod('string', function (str, msg) { - if (msg) flag(this, 'message', msg); - var obj = flag(this, 'object'); - new Assertion(obj, msg).is.a('string'); - - this.assert( - ~obj.indexOf(str) - , 'expected #{this} to contain ' + _.inspect(str) - , 'expected #{this} to not contain ' + _.inspect(str) - ); - }); - - - /** - * ### .keys(key1, [key2], [...]) - * - * Asserts that the target contains any or all of the passed-in keys. - * Use in combination with `any`, `all`, `contains`, or `have` will affect - * what will pass. - * - * When used in conjunction with `any`, at least one key that is passed - * in must exist in the target object. This is regardless whether or not - * the `have` or `contain` qualifiers are used. Note, either `any` or `all` - * should be used in the assertion. If neither are used, the assertion is - * defaulted to `all`. - * - * When both `all` and `contain` are used, the target object must have at - * least all of the passed-in keys but may have more keys not listed. - * - * When both `all` and `have` are used, the target object must both contain - * all of the passed-in keys AND the number of keys in the target object must - * match the number of keys passed in (in other words, a target object must - * have all and only all of the passed-in keys). - * - * expect({ foo: 1, bar: 2 }).to.have.any.keys('foo', 'baz'); - * expect({ foo: 1, bar: 2 }).to.have.any.keys('foo'); - * expect({ foo: 1, bar: 2 }).to.contain.any.keys('bar', 'baz'); - * expect({ foo: 1, bar: 2 }).to.contain.any.keys(['foo']); - * expect({ foo: 1, bar: 2 }).to.contain.any.keys({'foo': 6}); - * expect({ foo: 1, bar: 2 }).to.have.all.keys(['bar', 'foo']); - * expect({ foo: 1, bar: 2 }).to.have.all.keys({'bar': 6, 'foo', 7}); - * expect({ foo: 1, bar: 2, baz: 3 }).to.contain.all.keys(['bar', 'foo']); - * expect({ foo: 1, bar: 2, baz: 3 }).to.contain.all.keys([{'bar': 6}}]); - * - * - * @name keys - * @alias key - * @param {String...|Array|Object} keys - * @api public - */ - - function assertKeys (keys) { - var obj = flag(this, 'object') - , str - , ok = true - , mixedArgsMsg = 'keys must be given single argument of Array|Object|String, or multiple String arguments'; - - switch (_.type(keys)) { - case "array": - if (arguments.length > 1) throw (new Error(mixedArgsMsg)); - break; - case "object": - if (arguments.length > 1) throw (new Error(mixedArgsMsg)); - keys = Object.keys(keys); - break; - default: - keys = Array.prototype.slice.call(arguments); - } - - if (!keys.length) throw new Error('keys required'); - - var actual = Object.keys(obj) - , expected = keys - , len = keys.length - , any = flag(this, 'any') - , all = flag(this, 'all'); - - if (!any && !all) { - all = true; - } - - // Has any - if (any) { - var intersection = expected.filter(function(key) { - return ~actual.indexOf(key); - }); - ok = intersection.length > 0; - } - - // Has all - if (all) { - ok = keys.every(function(key){ - return ~actual.indexOf(key); - }); - if (!flag(this, 'negate') && !flag(this, 'contains')) { - ok = ok && keys.length == actual.length; - } - } - - // Key string - if (len > 1) { - keys = keys.map(function(key){ - return _.inspect(key); - }); - var last = keys.pop(); - if (all) { - str = keys.join(', ') + ', and ' + last; - } - if (any) { - str = keys.join(', ') + ', or ' + last; - } - } else { - str = _.inspect(keys[0]); - } - - // Form - str = (len > 1 ? 'keys ' : 'key ') + str; - - // Have / include - str = (flag(this, 'contains') ? 'contain ' : 'have ') + str; - - // Assertion - this.assert( - ok - , 'expected #{this} to ' + str - , 'expected #{this} to not ' + str - , expected.slice(0).sort() - , actual.sort() - , true - ); - } - - Assertion.addMethod('keys', assertKeys); - Assertion.addMethod('key', assertKeys); - - /** - * ### .throw(constructor) - * - * Asserts that the function target will throw a specific error, or specific type of error - * (as determined using `instanceof`), optionally with a RegExp or string inclusion test - * for the error's message. - * - * var err = new ReferenceError('This is a bad function.'); - * var fn = function () { throw err; } - * expect(fn).to.throw(ReferenceError); - * expect(fn).to.throw(Error); - * expect(fn).to.throw(/bad function/); - * expect(fn).to.not.throw('good function'); - * expect(fn).to.throw(ReferenceError, /bad function/); - * expect(fn).to.throw(err); - * expect(fn).to.not.throw(new RangeError('Out of range.')); - * - * Please note that when a throw expectation is negated, it will check each - * parameter independently, starting with error constructor type. The appropriate way - * to check for the existence of a type of error but for a message that does not match - * is to use `and`. - * - * expect(fn).to.throw(ReferenceError) - * .and.not.throw(/good function/); - * - * @name throw - * @alias throws - * @alias Throw - * @param {ErrorConstructor} constructor - * @param {String|RegExp} expected error message - * @param {String} message _optional_ - * @see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error#Error_types - * @returns error for chaining (null if no error) - * @api public - */ - - function assertThrows (constructor, errMsg, msg) { - if (msg) flag(this, 'message', msg); - var obj = flag(this, 'object'); - new Assertion(obj, msg).is.a('function'); - - var thrown = false - , desiredError = null - , name = null - , thrownError = null; - - if (arguments.length === 0) { - errMsg = null; - constructor = null; - } else if (constructor && (constructor instanceof RegExp || 'string' === typeof constructor)) { - errMsg = constructor; - constructor = null; - } else if (constructor && constructor instanceof Error) { - desiredError = constructor; - constructor = null; - errMsg = null; - } else if (typeof constructor === 'function') { - name = constructor.prototype.name || constructor.name; - if (name === 'Error' && constructor !== Error) { - name = (new constructor()).name; - } - } else { - constructor = null; - } - - try { - obj(); - } catch (err) { - // first, check desired error - if (desiredError) { - this.assert( - err === desiredError - , 'expected #{this} to throw #{exp} but #{act} was thrown' - , 'expected #{this} to not throw #{exp}' - , (desiredError instanceof Error ? desiredError.toString() : desiredError) - , (err instanceof Error ? err.toString() : err) - ); - - flag(this, 'object', err); - return this; - } - - // next, check constructor - if (constructor) { - this.assert( - err instanceof constructor - , 'expected #{this} to throw #{exp} but #{act} was thrown' - , 'expected #{this} to not throw #{exp} but #{act} was thrown' - , name - , (err instanceof Error ? err.toString() : err) - ); - - if (!errMsg) { - flag(this, 'object', err); - return this; - } - } - - // next, check message - var message = 'object' === _.type(err) && "message" in err - ? err.message - : '' + err; - - if ((message != null) && errMsg && errMsg instanceof RegExp) { - this.assert( - errMsg.exec(message) - , 'expected #{this} to throw error matching #{exp} but got #{act}' - , 'expected #{this} to throw error not matching #{exp}' - , errMsg - , message - ); - - flag(this, 'object', err); - return this; - } else if ((message != null) && errMsg && 'string' === typeof errMsg) { - this.assert( - ~message.indexOf(errMsg) - , 'expected #{this} to throw error including #{exp} but got #{act}' - , 'expected #{this} to throw error not including #{act}' - , errMsg - , message - ); - - flag(this, 'object', err); - return this; - } else { - thrown = true; - thrownError = err; - } - } - - var actuallyGot = '' - , expectedThrown = name !== null - ? name - : desiredError - ? '#{exp}' //_.inspect(desiredError) - : 'an error'; - - if (thrown) { - actuallyGot = ' but #{act} was thrown' - } - - this.assert( - thrown === true - , 'expected #{this} to throw ' + expectedThrown + actuallyGot - , 'expected #{this} to not throw ' + expectedThrown + actuallyGot - , (desiredError instanceof Error ? desiredError.toString() : desiredError) - , (thrownError instanceof Error ? thrownError.toString() : thrownError) - ); - - flag(this, 'object', thrownError); - }; - - Assertion.addMethod('throw', assertThrows); - Assertion.addMethod('throws', assertThrows); - Assertion.addMethod('Throw', assertThrows); - - /** - * ### .respondTo(method) - * - * Asserts that the object or class target will respond to a method. - * - * Klass.prototype.bar = function(){}; - * expect(Klass).to.respondTo('bar'); - * expect(obj).to.respondTo('bar'); - * - * To check if a constructor will respond to a static function, - * set the `itself` flag. - * - * Klass.baz = function(){}; - * expect(Klass).itself.to.respondTo('baz'); - * - * @name respondTo - * @param {String} method - * @param {String} message _optional_ - * @api public - */ - - Assertion.addMethod('respondTo', function (method, msg) { - if (msg) flag(this, 'message', msg); - var obj = flag(this, 'object') - , itself = flag(this, 'itself') - , context = ('function' === _.type(obj) && !itself) - ? obj.prototype[method] - : obj[method]; - - this.assert( - 'function' === typeof context - , 'expected #{this} to respond to ' + _.inspect(method) - , 'expected #{this} to not respond to ' + _.inspect(method) - ); - }); - - /** - * ### .itself - * - * Sets the `itself` flag, later used by the `respondTo` assertion. - * - * function Foo() {} - * Foo.bar = function() {} - * Foo.prototype.baz = function() {} - * - * expect(Foo).itself.to.respondTo('bar'); - * expect(Foo).itself.not.to.respondTo('baz'); - * - * @name itself - * @api public - */ - - Assertion.addProperty('itself', function () { - flag(this, 'itself', true); - }); - - /** - * ### .satisfy(method) - * - * Asserts that the target passes a given truth test. - * - * expect(1).to.satisfy(function(num) { return num > 0; }); - * - * @name satisfy - * @param {Function} matcher - * @param {String} message _optional_ - * @api public - */ - - Assertion.addMethod('satisfy', function (matcher, msg) { - if (msg) flag(this, 'message', msg); - var obj = flag(this, 'object'); - var result = matcher(obj); - this.assert( - result - , 'expected #{this} to satisfy ' + _.objDisplay(matcher) - , 'expected #{this} to not satisfy' + _.objDisplay(matcher) - , this.negate ? false : true - , result - ); - }); - - /** - * ### .closeTo(expected, delta) - * - * Asserts that the target is equal `expected`, to within a +/- `delta` range. - * - * expect(1.5).to.be.closeTo(1, 0.5); - * - * @name closeTo - * @param {Number} expected - * @param {Number} delta - * @param {String} message _optional_ - * @api public - */ - - Assertion.addMethod('closeTo', function (expected, delta, msg) { - if (msg) flag(this, 'message', msg); - var obj = flag(this, 'object'); - - new Assertion(obj, msg).is.a('number'); - if (_.type(expected) !== 'number' || _.type(delta) !== 'number') { - throw new Error('the arguments to closeTo must be numbers'); - } - - this.assert( - Math.abs(obj - expected) <= delta - , 'expected #{this} to be close to ' + expected + ' +/- ' + delta - , 'expected #{this} not to be close to ' + expected + ' +/- ' + delta - ); - }); - - function isSubsetOf(subset, superset, cmp) { - return subset.every(function(elem) { - if (!cmp) return superset.indexOf(elem) !== -1; - - return superset.some(function(elem2) { - return cmp(elem, elem2); - }); - }) - } - - /** - * ### .members(set) - * - * Asserts that the target is a superset of `set`, - * or that the target and `set` have the same strictly-equal (===) members. - * Alternately, if the `deep` flag is set, set members are compared for deep - * equality. - * - * expect([1, 2, 3]).to.include.members([3, 2]); - * expect([1, 2, 3]).to.not.include.members([3, 2, 8]); - * - * expect([4, 2]).to.have.members([2, 4]); - * expect([5, 2]).to.not.have.members([5, 2, 1]); - * - * expect([{ id: 1 }]).to.deep.include.members([{ id: 1 }]); - * - * @name members - * @param {Array} set - * @param {String} message _optional_ - * @api public - */ - - Assertion.addMethod('members', function (subset, msg) { - if (msg) flag(this, 'message', msg); - var obj = flag(this, 'object'); - - new Assertion(obj).to.be.an('array'); - new Assertion(subset).to.be.an('array'); - - var cmp = flag(this, 'deep') ? _.eql : undefined; - - if (flag(this, 'contains')) { - return this.assert( - isSubsetOf(subset, obj, cmp) - , 'expected #{this} to be a superset of #{act}' - , 'expected #{this} to not be a superset of #{act}' - , obj - , subset - ); - } - - this.assert( - isSubsetOf(obj, subset, cmp) && isSubsetOf(subset, obj, cmp) - , 'expected #{this} to have the same members as #{act}' - , 'expected #{this} to not have the same members as #{act}' - , obj - , subset - ); - }); - - /** - * ### .change(function) - * - * Asserts that a function changes an object property - * - * var obj = { val: 10 }; - * var fn = function() { obj.val += 3 }; - * var noChangeFn = function() { return 'foo' + 'bar'; } - * expect(fn).to.change(obj, 'val'); - * expect(noChangFn).to.not.change(obj, 'val') - * - * @name change - * @alias changes - * @alias Change - * @param {String} object - * @param {String} property name - * @param {String} message _optional_ - * @api public - */ - - function assertChanges (object, prop, msg) { - if (msg) flag(this, 'message', msg); - var fn = flag(this, 'object'); - new Assertion(object, msg).to.have.property(prop); - new Assertion(fn).is.a('function'); - - var initial = object[prop]; - fn(); - - this.assert( - initial !== object[prop] - , 'expected .' + prop + ' to change' - , 'expected .' + prop + ' to not change' - ); - } - - Assertion.addChainableMethod('change', assertChanges); - Assertion.addChainableMethod('changes', assertChanges); - - /** - * ### .increase(function) - * - * Asserts that a function increases an object property - * - * var obj = { val: 10 }; - * var fn = function() { obj.val = 15 }; - * expect(fn).to.increase(obj, 'val'); - * - * @name increase - * @alias increases - * @alias Increase - * @param {String} object - * @param {String} property name - * @param {String} message _optional_ - * @api public - */ - - function assertIncreases (object, prop, msg) { - if (msg) flag(this, 'message', msg); - var fn = flag(this, 'object'); - new Assertion(object, msg).to.have.property(prop); - new Assertion(fn).is.a('function'); - - var initial = object[prop]; - fn(); - - this.assert( - object[prop] - initial > 0 - , 'expected .' + prop + ' to increase' - , 'expected .' + prop + ' to not increase' - ); - } - - Assertion.addChainableMethod('increase', assertIncreases); - Assertion.addChainableMethod('increases', assertIncreases); - - /** - * ### .decrease(function) - * - * Asserts that a function decreases an object property - * - * var obj = { val: 10 }; - * var fn = function() { obj.val = 5 }; - * expect(fn).to.decrease(obj, 'val'); - * - * @name decrease - * @alias decreases - * @alias Decrease - * @param {String} object - * @param {String} property name - * @param {String} message _optional_ - * @api public - */ - - function assertDecreases (object, prop, msg) { - if (msg) flag(this, 'message', msg); - var fn = flag(this, 'object'); - new Assertion(object, msg).to.have.property(prop); - new Assertion(fn).is.a('function'); - - var initial = object[prop]; - fn(); - - this.assert( - object[prop] - initial < 0 - , 'expected .' + prop + ' to decrease' - , 'expected .' + prop + ' to not decrease' - ); - } - - Assertion.addChainableMethod('decrease', assertDecreases); - Assertion.addChainableMethod('decreases', assertDecreases); - -}; - -}); - -require.register("chai/lib/chai/interface/assert.js", function (exports, module) { -/*! - * chai - * Copyright(c) 2011-2014 Jake Luer - * MIT Licensed - */ - - -module.exports = function (chai, util) { - - /*! - * Chai dependencies. - */ - - var Assertion = chai.Assertion - , flag = util.flag; - - /*! - * Module export. - */ - - /** - * ### assert(expression, message) - * - * Write your own test expressions. - * - * assert('foo' !== 'bar', 'foo is not bar'); - * assert(Array.isArray([]), 'empty arrays are arrays'); - * - * @param {Mixed} expression to test for truthiness - * @param {String} message to display on error - * @name assert - * @api public - */ - - var assert = chai.assert = function (express, errmsg) { - var test = new Assertion(null, null, chai.assert); - test.assert( - express - , errmsg - , '[ negation message unavailable ]' - ); - }; - - /** - * ### .fail(actual, expected, [message], [operator]) - * - * Throw a failure. Node.js `assert` module-compatible. - * - * @name fail - * @param {Mixed} actual - * @param {Mixed} expected - * @param {String} message - * @param {String} operator - * @api public - */ - - assert.fail = function (actual, expected, message, operator) { - message = message || 'assert.fail()'; - throw new chai.AssertionError(message, { - actual: actual - , expected: expected - , operator: operator - }, assert.fail); - }; - - /** - * ### .ok(object, [message]) - * - * Asserts that `object` is truthy. - * - * assert.ok('everything', 'everything is ok'); - * assert.ok(false, 'this will fail'); - * - * @name ok - * @param {Mixed} object to test - * @param {String} message - * @api public - */ - - assert.ok = function (val, msg) { - new Assertion(val, msg).is.ok; - }; - - /** - * ### .notOk(object, [message]) - * - * Asserts that `object` is falsy. - * - * assert.notOk('everything', 'this will fail'); - * assert.notOk(false, 'this will pass'); - * - * @name notOk - * @param {Mixed} object to test - * @param {String} message - * @api public - */ - - assert.notOk = function (val, msg) { - new Assertion(val, msg).is.not.ok; - }; - - /** - * ### .equal(actual, expected, [message]) - * - * Asserts non-strict equality (`==`) of `actual` and `expected`. - * - * assert.equal(3, '3', '== coerces values to strings'); - * - * @name equal - * @param {Mixed} actual - * @param {Mixed} expected - * @param {String} message - * @api public - */ - - assert.equal = function (act, exp, msg) { - var test = new Assertion(act, msg, assert.equal); - - test.assert( - exp == flag(test, 'object') - , 'expected #{this} to equal #{exp}' - , 'expected #{this} to not equal #{act}' - , exp - , act - ); - }; - - /** - * ### .notEqual(actual, expected, [message]) - * - * Asserts non-strict inequality (`!=`) of `actual` and `expected`. - * - * assert.notEqual(3, 4, 'these numbers are not equal'); - * - * @name notEqual - * @param {Mixed} actual - * @param {Mixed} expected - * @param {String} message - * @api public - */ - - assert.notEqual = function (act, exp, msg) { - var test = new Assertion(act, msg, assert.notEqual); - - test.assert( - exp != flag(test, 'object') - , 'expected #{this} to not equal #{exp}' - , 'expected #{this} to equal #{act}' - , exp - , act - ); - }; - - /** - * ### .strictEqual(actual, expected, [message]) - * - * Asserts strict equality (`===`) of `actual` and `expected`. - * - * assert.strictEqual(true, true, 'these booleans are strictly equal'); - * - * @name strictEqual - * @param {Mixed} actual - * @param {Mixed} expected - * @param {String} message - * @api public - */ - - assert.strictEqual = function (act, exp, msg) { - new Assertion(act, msg).to.equal(exp); - }; - - /** - * ### .notStrictEqual(actual, expected, [message]) - * - * Asserts strict inequality (`!==`) of `actual` and `expected`. - * - * assert.notStrictEqual(3, '3', 'no coercion for strict equality'); - * - * @name notStrictEqual - * @param {Mixed} actual - * @param {Mixed} expected - * @param {String} message - * @api public - */ - - assert.notStrictEqual = function (act, exp, msg) { - new Assertion(act, msg).to.not.equal(exp); - }; - - /** - * ### .deepEqual(actual, expected, [message]) - * - * Asserts that `actual` is deeply equal to `expected`. - * - * assert.deepEqual({ tea: 'green' }, { tea: 'green' }); - * - * @name deepEqual - * @param {Mixed} actual - * @param {Mixed} expected - * @param {String} message - * @api public - */ - - assert.deepEqual = function (act, exp, msg) { - new Assertion(act, msg).to.eql(exp); - }; - - /** - * ### .notDeepEqual(actual, expected, [message]) - * - * Assert that `actual` is not deeply equal to `expected`. - * - * assert.notDeepEqual({ tea: 'green' }, { tea: 'jasmine' }); - * - * @name notDeepEqual - * @param {Mixed} actual - * @param {Mixed} expected - * @param {String} message - * @api public - */ - - assert.notDeepEqual = function (act, exp, msg) { - new Assertion(act, msg).to.not.eql(exp); - }; - - /** - * ### .isTrue(value, [message]) - * - * Asserts that `value` is true. - * - * var teaServed = true; - * assert.isTrue(teaServed, 'the tea has been served'); - * - * @name isTrue - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.isAbove = function (val, abv, msg) { - new Assertion(val, msg).to.be.above(abv); - }; - - /** - * ### .isAbove(valueToCheck, valueToBeAbove, [message]) - * - * Asserts `valueToCheck` is strictly greater than (>) `valueToBeAbove` - * - * assert.isAbove(5, 2, '5 is strictly greater than 2'); - * - * @name isAbove - * @param {Mixed} valueToCheck - * @param {Mixed} valueToBeAbove - * @param {String} message - * @api public - */ - - assert.isBelow = function (val, blw, msg) { - new Assertion(val, msg).to.be.below(blw); - }; - - /** - * ### .isBelow(valueToCheck, valueToBeBelow, [message]) - * - * Asserts `valueToCheck` is strictly less than (<) `valueToBeBelow` - * - * assert.isBelow(3, 6, '3 is strictly less than 6'); - * - * @name isBelow - * @param {Mixed} valueToCheck - * @param {Mixed} valueToBeBelow - * @param {String} message - * @api public - */ - - assert.isTrue = function (val, msg) { - new Assertion(val, msg).is['true']; - }; - - /** - * ### .isFalse(value, [message]) - * - * Asserts that `value` is false. - * - * var teaServed = false; - * assert.isFalse(teaServed, 'no tea yet? hmm...'); - * - * @name isFalse - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.isFalse = function (val, msg) { - new Assertion(val, msg).is['false']; - }; - - /** - * ### .isNull(value, [message]) - * - * Asserts that `value` is null. - * - * assert.isNull(err, 'there was no error'); - * - * @name isNull - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.isNull = function (val, msg) { - new Assertion(val, msg).to.equal(null); - }; - - /** - * ### .isNotNull(value, [message]) - * - * Asserts that `value` is not null. - * - * var tea = 'tasty chai'; - * assert.isNotNull(tea, 'great, time for tea!'); - * - * @name isNotNull - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.isNotNull = function (val, msg) { - new Assertion(val, msg).to.not.equal(null); - }; - - /** - * ### .isUndefined(value, [message]) - * - * Asserts that `value` is `undefined`. - * - * var tea; - * assert.isUndefined(tea, 'no tea defined'); - * - * @name isUndefined - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.isUndefined = function (val, msg) { - new Assertion(val, msg).to.equal(undefined); - }; - - /** - * ### .isDefined(value, [message]) - * - * Asserts that `value` is not `undefined`. - * - * var tea = 'cup of chai'; - * assert.isDefined(tea, 'tea has been defined'); - * - * @name isDefined - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.isDefined = function (val, msg) { - new Assertion(val, msg).to.not.equal(undefined); - }; - - /** - * ### .isFunction(value, [message]) - * - * Asserts that `value` is a function. - * - * function serveTea() { return 'cup of tea'; }; - * assert.isFunction(serveTea, 'great, we can have tea now'); - * - * @name isFunction - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.isFunction = function (val, msg) { - new Assertion(val, msg).to.be.a('function'); - }; - - /** - * ### .isNotFunction(value, [message]) - * - * Asserts that `value` is _not_ a function. - * - * var serveTea = [ 'heat', 'pour', 'sip' ]; - * assert.isNotFunction(serveTea, 'great, we have listed the steps'); - * - * @name isNotFunction - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.isNotFunction = function (val, msg) { - new Assertion(val, msg).to.not.be.a('function'); - }; - - /** - * ### .isObject(value, [message]) - * - * Asserts that `value` is an object (as revealed by - * `Object.prototype.toString`). - * - * var selection = { name: 'Chai', serve: 'with spices' }; - * assert.isObject(selection, 'tea selection is an object'); - * - * @name isObject - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.isObject = function (val, msg) { - new Assertion(val, msg).to.be.a('object'); - }; - - /** - * ### .isNotObject(value, [message]) - * - * Asserts that `value` is _not_ an object. - * - * var selection = 'chai' - * assert.isNotObject(selection, 'tea selection is not an object'); - * assert.isNotObject(null, 'null is not an object'); - * - * @name isNotObject - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.isNotObject = function (val, msg) { - new Assertion(val, msg).to.not.be.a('object'); - }; - - /** - * ### .isArray(value, [message]) - * - * Asserts that `value` is an array. - * - * var menu = [ 'green', 'chai', 'oolong' ]; - * assert.isArray(menu, 'what kind of tea do we want?'); - * - * @name isArray - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.isArray = function (val, msg) { - new Assertion(val, msg).to.be.an('array'); - }; - - /** - * ### .isNotArray(value, [message]) - * - * Asserts that `value` is _not_ an array. - * - * var menu = 'green|chai|oolong'; - * assert.isNotArray(menu, 'what kind of tea do we want?'); - * - * @name isNotArray - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.isNotArray = function (val, msg) { - new Assertion(val, msg).to.not.be.an('array'); - }; - - /** - * ### .isString(value, [message]) - * - * Asserts that `value` is a string. - * - * var teaOrder = 'chai'; - * assert.isString(teaOrder, 'order placed'); - * - * @name isString - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.isString = function (val, msg) { - new Assertion(val, msg).to.be.a('string'); - }; - - /** - * ### .isNotString(value, [message]) - * - * Asserts that `value` is _not_ a string. - * - * var teaOrder = 4; - * assert.isNotString(teaOrder, 'order placed'); - * - * @name isNotString - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.isNotString = function (val, msg) { - new Assertion(val, msg).to.not.be.a('string'); - }; - - /** - * ### .isNumber(value, [message]) - * - * Asserts that `value` is a number. - * - * var cups = 2; - * assert.isNumber(cups, 'how many cups'); - * - * @name isNumber - * @param {Number} value - * @param {String} message - * @api public - */ - - assert.isNumber = function (val, msg) { - new Assertion(val, msg).to.be.a('number'); - }; - - /** - * ### .isNotNumber(value, [message]) - * - * Asserts that `value` is _not_ a number. - * - * var cups = '2 cups please'; - * assert.isNotNumber(cups, 'how many cups'); - * - * @name isNotNumber - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.isNotNumber = function (val, msg) { - new Assertion(val, msg).to.not.be.a('number'); - }; - - /** - * ### .isBoolean(value, [message]) - * - * Asserts that `value` is a boolean. - * - * var teaReady = true - * , teaServed = false; - * - * assert.isBoolean(teaReady, 'is the tea ready'); - * assert.isBoolean(teaServed, 'has tea been served'); - * - * @name isBoolean - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.isBoolean = function (val, msg) { - new Assertion(val, msg).to.be.a('boolean'); - }; - - /** - * ### .isNotBoolean(value, [message]) - * - * Asserts that `value` is _not_ a boolean. - * - * var teaReady = 'yep' - * , teaServed = 'nope'; - * - * assert.isNotBoolean(teaReady, 'is the tea ready'); - * assert.isNotBoolean(teaServed, 'has tea been served'); - * - * @name isNotBoolean - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.isNotBoolean = function (val, msg) { - new Assertion(val, msg).to.not.be.a('boolean'); - }; - - /** - * ### .typeOf(value, name, [message]) - * - * Asserts that `value`'s type is `name`, as determined by - * `Object.prototype.toString`. - * - * assert.typeOf({ tea: 'chai' }, 'object', 'we have an object'); - * assert.typeOf(['chai', 'jasmine'], 'array', 'we have an array'); - * assert.typeOf('tea', 'string', 'we have a string'); - * assert.typeOf(/tea/, 'regexp', 'we have a regular expression'); - * assert.typeOf(null, 'null', 'we have a null'); - * assert.typeOf(undefined, 'undefined', 'we have an undefined'); - * - * @name typeOf - * @param {Mixed} value - * @param {String} name - * @param {String} message - * @api public - */ - - assert.typeOf = function (val, type, msg) { - new Assertion(val, msg).to.be.a(type); - }; - - /** - * ### .notTypeOf(value, name, [message]) - * - * Asserts that `value`'s type is _not_ `name`, as determined by - * `Object.prototype.toString`. - * - * assert.notTypeOf('tea', 'number', 'strings are not numbers'); - * - * @name notTypeOf - * @param {Mixed} value - * @param {String} typeof name - * @param {String} message - * @api public - */ - - assert.notTypeOf = function (val, type, msg) { - new Assertion(val, msg).to.not.be.a(type); - }; - - /** - * ### .instanceOf(object, constructor, [message]) - * - * Asserts that `value` is an instance of `constructor`. - * - * var Tea = function (name) { this.name = name; } - * , chai = new Tea('chai'); - * - * assert.instanceOf(chai, Tea, 'chai is an instance of tea'); - * - * @name instanceOf - * @param {Object} object - * @param {Constructor} constructor - * @param {String} message - * @api public - */ - - assert.instanceOf = function (val, type, msg) { - new Assertion(val, msg).to.be.instanceOf(type); - }; - - /** - * ### .notInstanceOf(object, constructor, [message]) - * - * Asserts `value` is not an instance of `constructor`. - * - * var Tea = function (name) { this.name = name; } - * , chai = new String('chai'); - * - * assert.notInstanceOf(chai, Tea, 'chai is not an instance of tea'); - * - * @name notInstanceOf - * @param {Object} object - * @param {Constructor} constructor - * @param {String} message - * @api public - */ - - assert.notInstanceOf = function (val, type, msg) { - new Assertion(val, msg).to.not.be.instanceOf(type); - }; - - /** - * ### .include(haystack, needle, [message]) - * - * Asserts that `haystack` includes `needle`. Works - * for strings and arrays. - * - * assert.include('foobar', 'bar', 'foobar contains string "bar"'); - * assert.include([ 1, 2, 3 ], 3, 'array contains value'); - * - * @name include - * @param {Array|String} haystack - * @param {Mixed} needle - * @param {String} message - * @api public - */ - - assert.include = function (exp, inc, msg) { - new Assertion(exp, msg, assert.include).include(inc); - }; - - /** - * ### .notInclude(haystack, needle, [message]) - * - * Asserts that `haystack` does not include `needle`. Works - * for strings and arrays. - *i - * assert.notInclude('foobar', 'baz', 'string not include substring'); - * assert.notInclude([ 1, 2, 3 ], 4, 'array not include contain value'); - * - * @name notInclude - * @param {Array|String} haystack - * @param {Mixed} needle - * @param {String} message - * @api public - */ - - assert.notInclude = function (exp, inc, msg) { - new Assertion(exp, msg, assert.notInclude).not.include(inc); - }; - - /** - * ### .match(value, regexp, [message]) - * - * Asserts that `value` matches the regular expression `regexp`. - * - * assert.match('foobar', /^foo/, 'regexp matches'); - * - * @name match - * @param {Mixed} value - * @param {RegExp} regexp - * @param {String} message - * @api public - */ - - assert.match = function (exp, re, msg) { - new Assertion(exp, msg).to.match(re); - }; - - /** - * ### .notMatch(value, regexp, [message]) - * - * Asserts that `value` does not match the regular expression `regexp`. - * - * assert.notMatch('foobar', /^foo/, 'regexp does not match'); - * - * @name notMatch - * @param {Mixed} value - * @param {RegExp} regexp - * @param {String} message - * @api public - */ - - assert.notMatch = function (exp, re, msg) { - new Assertion(exp, msg).to.not.match(re); - }; - - /** - * ### .property(object, property, [message]) - * - * Asserts that `object` has a property named by `property`. - * - * assert.property({ tea: { green: 'matcha' }}, 'tea'); - * - * @name property - * @param {Object} object - * @param {String} property - * @param {String} message - * @api public - */ - - assert.property = function (obj, prop, msg) { - new Assertion(obj, msg).to.have.property(prop); - }; - - /** - * ### .notProperty(object, property, [message]) - * - * Asserts that `object` does _not_ have a property named by `property`. - * - * assert.notProperty({ tea: { green: 'matcha' }}, 'coffee'); - * - * @name notProperty - * @param {Object} object - * @param {String} property - * @param {String} message - * @api public - */ - - assert.notProperty = function (obj, prop, msg) { - new Assertion(obj, msg).to.not.have.property(prop); - }; - - /** - * ### .deepProperty(object, property, [message]) - * - * Asserts that `object` has a property named by `property`, which can be a - * string using dot- and bracket-notation for deep reference. - * - * assert.deepProperty({ tea: { green: 'matcha' }}, 'tea.green'); - * - * @name deepProperty - * @param {Object} object - * @param {String} property - * @param {String} message - * @api public - */ - - assert.deepProperty = function (obj, prop, msg) { - new Assertion(obj, msg).to.have.deep.property(prop); - }; - - /** - * ### .notDeepProperty(object, property, [message]) - * - * Asserts that `object` does _not_ have a property named by `property`, which - * can be a string using dot- and bracket-notation for deep reference. - * - * assert.notDeepProperty({ tea: { green: 'matcha' }}, 'tea.oolong'); - * - * @name notDeepProperty - * @param {Object} object - * @param {String} property - * @param {String} message - * @api public - */ - - assert.notDeepProperty = function (obj, prop, msg) { - new Assertion(obj, msg).to.not.have.deep.property(prop); - }; - - /** - * ### .propertyVal(object, property, value, [message]) - * - * Asserts that `object` has a property named by `property` with value given - * by `value`. - * - * assert.propertyVal({ tea: 'is good' }, 'tea', 'is good'); - * - * @name propertyVal - * @param {Object} object - * @param {String} property - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.propertyVal = function (obj, prop, val, msg) { - new Assertion(obj, msg).to.have.property(prop, val); - }; - - /** - * ### .propertyNotVal(object, property, value, [message]) - * - * Asserts that `object` has a property named by `property`, but with a value - * different from that given by `value`. - * - * assert.propertyNotVal({ tea: 'is good' }, 'tea', 'is bad'); - * - * @name propertyNotVal - * @param {Object} object - * @param {String} property - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.propertyNotVal = function (obj, prop, val, msg) { - new Assertion(obj, msg).to.not.have.property(prop, val); - }; - - /** - * ### .deepPropertyVal(object, property, value, [message]) - * - * Asserts that `object` has a property named by `property` with value given - * by `value`. `property` can use dot- and bracket-notation for deep - * reference. - * - * assert.deepPropertyVal({ tea: { green: 'matcha' }}, 'tea.green', 'matcha'); - * - * @name deepPropertyVal - * @param {Object} object - * @param {String} property - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.deepPropertyVal = function (obj, prop, val, msg) { - new Assertion(obj, msg).to.have.deep.property(prop, val); - }; - - /** - * ### .deepPropertyNotVal(object, property, value, [message]) - * - * Asserts that `object` has a property named by `property`, but with a value - * different from that given by `value`. `property` can use dot- and - * bracket-notation for deep reference. - * - * assert.deepPropertyNotVal({ tea: { green: 'matcha' }}, 'tea.green', 'konacha'); - * - * @name deepPropertyNotVal - * @param {Object} object - * @param {String} property - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.deepPropertyNotVal = function (obj, prop, val, msg) { - new Assertion(obj, msg).to.not.have.deep.property(prop, val); - }; - - /** - * ### .lengthOf(object, length, [message]) - * - * Asserts that `object` has a `length` property with the expected value. - * - * assert.lengthOf([1,2,3], 3, 'array has length of 3'); - * assert.lengthOf('foobar', 5, 'string has length of 6'); - * - * @name lengthOf - * @param {Mixed} object - * @param {Number} length - * @param {String} message - * @api public - */ - - assert.lengthOf = function (exp, len, msg) { - new Assertion(exp, msg).to.have.length(len); - }; - - /** - * ### .throws(function, [constructor/string/regexp], [string/regexp], [message]) - * - * Asserts that `function` will throw an error that is an instance of - * `constructor`, or alternately that it will throw an error with message - * matching `regexp`. - * - * assert.throw(fn, 'function throws a reference error'); - * assert.throw(fn, /function throws a reference error/); - * assert.throw(fn, ReferenceError); - * assert.throw(fn, ReferenceError, 'function throws a reference error'); - * assert.throw(fn, ReferenceError, /function throws a reference error/); - * - * @name throws - * @alias throw - * @alias Throw - * @param {Function} function - * @param {ErrorConstructor} constructor - * @param {RegExp} regexp - * @param {String} message - * @see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error#Error_types - * @api public - */ - - assert.Throw = function (fn, errt, errs, msg) { - if ('string' === typeof errt || errt instanceof RegExp) { - errs = errt; - errt = null; - } - - var assertErr = new Assertion(fn, msg).to.Throw(errt, errs); - return flag(assertErr, 'object'); - }; - - /** - * ### .doesNotThrow(function, [constructor/regexp], [message]) - * - * Asserts that `function` will _not_ throw an error that is an instance of - * `constructor`, or alternately that it will not throw an error with message - * matching `regexp`. - * - * assert.doesNotThrow(fn, Error, 'function does not throw'); - * - * @name doesNotThrow - * @param {Function} function - * @param {ErrorConstructor} constructor - * @param {RegExp} regexp - * @param {String} message - * @see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error#Error_types - * @api public - */ - - assert.doesNotThrow = function (fn, type, msg) { - if ('string' === typeof type) { - msg = type; - type = null; - } - - new Assertion(fn, msg).to.not.Throw(type); - }; - - /** - * ### .operator(val1, operator, val2, [message]) - * - * Compares two values using `operator`. - * - * assert.operator(1, '<', 2, 'everything is ok'); - * assert.operator(1, '>', 2, 'this will fail'); - * - * @name operator - * @param {Mixed} val1 - * @param {String} operator - * @param {Mixed} val2 - * @param {String} message - * @api public - */ - - assert.operator = function (val, operator, val2, msg) { - if (!~['==', '===', '>', '>=', '<', '<=', '!=', '!=='].indexOf(operator)) { - throw new Error('Invalid operator "' + operator + '"'); - } - var test = new Assertion(eval(val + operator + val2), msg); - test.assert( - true === flag(test, 'object') - , 'expected ' + util.inspect(val) + ' to be ' + operator + ' ' + util.inspect(val2) - , 'expected ' + util.inspect(val) + ' to not be ' + operator + ' ' + util.inspect(val2) ); - }; - - /** - * ### .closeTo(actual, expected, delta, [message]) - * - * Asserts that the target is equal `expected`, to within a +/- `delta` range. - * - * assert.closeTo(1.5, 1, 0.5, 'numbers are close'); - * - * @name closeTo - * @param {Number} actual - * @param {Number} expected - * @param {Number} delta - * @param {String} message - * @api public - */ - - assert.closeTo = function (act, exp, delta, msg) { - new Assertion(act, msg).to.be.closeTo(exp, delta); - }; - - /** - * ### .sameMembers(set1, set2, [message]) - * - * Asserts that `set1` and `set2` have the same members. - * Order is not taken into account. - * - * assert.sameMembers([ 1, 2, 3 ], [ 2, 1, 3 ], 'same members'); - * - * @name sameMembers - * @param {Array} set1 - * @param {Array} set2 - * @param {String} message - * @api public - */ - - assert.sameMembers = function (set1, set2, msg) { - new Assertion(set1, msg).to.have.same.members(set2); - } - - /** - * ### .sameDeepMembers(set1, set2, [message]) - * - * Asserts that `set1` and `set2` have the same members - using a deep equality checking. - * Order is not taken into account. - * - * assert.sameDeepMembers([ {b: 3}, {a: 2}, {c: 5} ], [ {c: 5}, {b: 3}, {a: 2} ], 'same deep members'); - * - * @name sameDeepMembers - * @param {Array} set1 - * @param {Array} set2 - * @param {String} message - * @api public - */ - - assert.sameDeepMembers = function (set1, set2, msg) { - new Assertion(set1, msg).to.have.same.deep.members(set2); - } - - /** - * ### .includeMembers(superset, subset, [message]) - * - * Asserts that `subset` is included in `superset`. - * Order is not taken into account. - * - * assert.includeMembers([ 1, 2, 3 ], [ 2, 1 ], 'include members'); - * - * @name includeMembers - * @param {Array} superset - * @param {Array} subset - * @param {String} message - * @api public - */ - - assert.includeMembers = function (superset, subset, msg) { - new Assertion(superset, msg).to.include.members(subset); - } - - /** - * ### .changes(function, object, property) - * - * Asserts that a function changes the value of a property - * - * var obj = { val: 10 }; - * var fn = function() { obj.val = 22 }; - * assert.changes(fn, obj, 'val'); - * - * @name changes - * @param {Function} modifier function - * @param {Object} object - * @param {String} property name - * @param {String} message _optional_ - * @api public - */ - - assert.changes = function (fn, obj, prop) { - new Assertion(fn).to.change(obj, prop); - } - - /** - * ### .doesNotChange(function, object, property) - * - * Asserts that a function does not changes the value of a property - * - * var obj = { val: 10 }; - * var fn = function() { console.log('foo'); }; - * assert.doesNotChange(fn, obj, 'val'); - * - * @name doesNotChange - * @param {Function} modifier function - * @param {Object} object - * @param {String} property name - * @param {String} message _optional_ - * @api public - */ - - assert.doesNotChange = function (fn, obj, prop) { - new Assertion(fn).to.not.change(obj, prop); - } - - /** - * ### .increases(function, object, property) - * - * Asserts that a function increases an object property - * - * var obj = { val: 10 }; - * var fn = function() { obj.val = 13 }; - * assert.increases(fn, obj, 'val'); - * - * @name increases - * @param {Function} modifier function - * @param {Object} object - * @param {String} property name - * @param {String} message _optional_ - * @api public - */ - - assert.increases = function (fn, obj, prop) { - new Assertion(fn).to.increase(obj, prop); - } - - /** - * ### .doesNotIncrease(function, object, property) - * - * Asserts that a function does not increase object property - * - * var obj = { val: 10 }; - * var fn = function() { obj.val = 8 }; - * assert.doesNotIncrease(fn, obj, 'val'); - * - * @name doesNotIncrease - * @param {Function} modifier function - * @param {Object} object - * @param {String} property name - * @param {String} message _optional_ - * @api public - */ - - assert.doesNotIncrease = function (fn, obj, prop) { - new Assertion(fn).to.not.increase(obj, prop); - } - - /** - * ### .decreases(function, object, property) - * - * Asserts that a function decreases an object property - * - * var obj = { val: 10 }; - * var fn = function() { obj.val = 5 }; - * assert.decreases(fn, obj, 'val'); - * - * @name decreases - * @param {Function} modifier function - * @param {Object} object - * @param {String} property name - * @param {String} message _optional_ - * @api public - */ - - assert.decreases = function (fn, obj, prop) { - new Assertion(fn).to.decrease(obj, prop); - } - - /** - * ### .doesNotDecrease(function, object, property) - * - * Asserts that a function does not decreases an object property - * - * var obj = { val: 10 }; - * var fn = function() { obj.val = 15 }; - * assert.doesNotDecrease(fn, obj, 'val'); - * - * @name doesNotDecrease - * @param {Function} modifier function - * @param {Object} object - * @param {String} property name - * @param {String} message _optional_ - * @api public - */ - - assert.doesNotDecrease = function (fn, obj, prop) { - new Assertion(fn).to.not.decrease(obj, prop); - } - - /*! - * Undocumented / untested - */ - - assert.ifError = function (val, msg) { - new Assertion(val, msg).to.not.be.ok; - }; - - /*! - * Aliases. - */ - - (function alias(name, as){ - assert[as] = assert[name]; - return alias; - }) - ('Throw', 'throw') - ('Throw', 'throws'); -}; - -}); - -require.register("chai/lib/chai/interface/expect.js", function (exports, module) { -/*! - * chai - * Copyright(c) 2011-2014 Jake Luer - * MIT Licensed - */ - -module.exports = function (chai, util) { - chai.expect = function (val, message) { - return new chai.Assertion(val, message); - }; - - /** - * ### .fail(actual, expected, [message], [operator]) - * - * Throw a failure. - * - * @name fail - * @param {Mixed} actual - * @param {Mixed} expected - * @param {String} message - * @param {String} operator - * @api public - */ - - chai.expect.fail = function (actual, expected, message, operator) { - message = message || 'expect.fail()'; - throw new chai.AssertionError(message, { - actual: actual - , expected: expected - , operator: operator - }, chai.expect.fail); - }; -}; - -}); - -require.register("chai/lib/chai/interface/should.js", function (exports, module) { -/*! - * chai - * Copyright(c) 2011-2014 Jake Luer - * MIT Licensed - */ - -module.exports = function (chai, util) { - var Assertion = chai.Assertion; - - function loadShould () { - // explicitly define this method as function as to have it's name to include as `ssfi` - function shouldGetter() { - if (this instanceof String || this instanceof Number) { - return new Assertion(this.constructor(this), null, shouldGetter); - } else if (this instanceof Boolean) { - return new Assertion(this == true, null, shouldGetter); - } - return new Assertion(this, null, shouldGetter); - } - function shouldSetter(value) { - // See https://github.com/chaijs/chai/issues/86: this makes - // `whatever.should = someValue` actually set `someValue`, which is - // especially useful for `global.should = require('chai').should()`. - // - // Note that we have to use [[DefineProperty]] instead of [[Put]] - // since otherwise we would trigger this very setter! - Object.defineProperty(this, 'should', { - value: value, - enumerable: true, - configurable: true, - writable: true - }); - } - // modify Object.prototype to have `should` - Object.defineProperty(Object.prototype, 'should', { - set: shouldSetter - , get: shouldGetter - , configurable: true - }); - - var should = {}; - - /** - * ### .fail(actual, expected, [message], [operator]) - * - * Throw a failure. - * - * @name fail - * @param {Mixed} actual - * @param {Mixed} expected - * @param {String} message - * @param {String} operator - * @api public - */ - - should.fail = function (actual, expected, message, operator) { - message = message || 'should.fail()'; - throw new chai.AssertionError(message, { - actual: actual - , expected: expected - , operator: operator - }, should.fail); - }; - - should.equal = function (val1, val2, msg) { - new Assertion(val1, msg).to.equal(val2); - }; - - should.Throw = function (fn, errt, errs, msg) { - new Assertion(fn, msg).to.Throw(errt, errs); - }; - - should.exist = function (val, msg) { - new Assertion(val, msg).to.exist; - } - - // negation - should.not = {} - - should.not.equal = function (val1, val2, msg) { - new Assertion(val1, msg).to.not.equal(val2); - }; - - should.not.Throw = function (fn, errt, errs, msg) { - new Assertion(fn, msg).to.not.Throw(errt, errs); - }; - - should.not.exist = function (val, msg) { - new Assertion(val, msg).to.not.exist; - } - - should['throw'] = should['Throw']; - should.not['throw'] = should.not['Throw']; - - return should; - }; - - chai.should = loadShould; - chai.Should = loadShould; -}; - -}); - -require.register("chai/lib/chai/utils/addChainableMethod.js", function (exports, module) { -/*! - * Chai - addChainingMethod utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ - -/*! - * Module dependencies - */ - -var transferFlags = require('chai/lib/chai/utils/transferFlags.js'); -var flag = require('chai/lib/chai/utils/flag.js'); -var config = require('chai/lib/chai/config.js'); - -/*! - * Module variables - */ - -// Check whether `__proto__` is supported -var hasProtoSupport = '__proto__' in Object; - -// Without `__proto__` support, this module will need to add properties to a function. -// However, some Function.prototype methods cannot be overwritten, -// and there seems no easy cross-platform way to detect them (@see chaijs/chai/issues/69). -var excludeNames = /^(?:length|name|arguments|caller)$/; - -// Cache `Function` properties -var call = Function.prototype.call, - apply = Function.prototype.apply; - -/** - * ### addChainableMethod (ctx, name, method, chainingBehavior) - * - * Adds a method to an object, such that the method can also be chained. - * - * utils.addChainableMethod(chai.Assertion.prototype, 'foo', function (str) { - * var obj = utils.flag(this, 'object'); - * new chai.Assertion(obj).to.be.equal(str); - * }); - * - * Can also be accessed directly from `chai.Assertion`. - * - * chai.Assertion.addChainableMethod('foo', fn, chainingBehavior); - * - * The result can then be used as both a method assertion, executing both `method` and - * `chainingBehavior`, or as a language chain, which only executes `chainingBehavior`. - * - * expect(fooStr).to.be.foo('bar'); - * expect(fooStr).to.be.foo.equal('foo'); - * - * @param {Object} ctx object to which the method is added - * @param {String} name of method to add - * @param {Function} method function to be used for `name`, when called - * @param {Function} chainingBehavior function to be called every time the property is accessed - * @name addChainableMethod - * @api public - */ - -module.exports = function (ctx, name, method, chainingBehavior) { - if (typeof chainingBehavior !== 'function') { - chainingBehavior = function () { }; - } - - var chainableBehavior = { - method: method - , chainingBehavior: chainingBehavior - }; - - // save the methods so we can overwrite them later, if we need to. - if (!ctx.__methods) { - ctx.__methods = {}; - } - ctx.__methods[name] = chainableBehavior; - - Object.defineProperty(ctx, name, - { get: function () { - chainableBehavior.chainingBehavior.call(this); - - var assert = function assert() { - var old_ssfi = flag(this, 'ssfi'); - if (old_ssfi && config.includeStack === false) - flag(this, 'ssfi', assert); - var result = chainableBehavior.method.apply(this, arguments); - return result === undefined ? this : result; - }; - - // Use `__proto__` if available - if (hasProtoSupport) { - // Inherit all properties from the object by replacing the `Function` prototype - var prototype = assert.__proto__ = Object.create(this); - // Restore the `call` and `apply` methods from `Function` - prototype.call = call; - prototype.apply = apply; - } - // Otherwise, redefine all properties (slow!) - else { - var asserterNames = Object.getOwnPropertyNames(ctx); - asserterNames.forEach(function (asserterName) { - if (!excludeNames.test(asserterName)) { - var pd = Object.getOwnPropertyDescriptor(ctx, asserterName); - Object.defineProperty(assert, asserterName, pd); - } - }); - } - - transferFlags(this, assert); - return assert; - } - , configurable: true - }); -}; - -}); - -require.register("chai/lib/chai/utils/addMethod.js", function (exports, module) { -/*! - * Chai - addMethod utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ - -var config = require('chai/lib/chai/config.js'); - -/** - * ### .addMethod (ctx, name, method) - * - * Adds a method to the prototype of an object. - * - * utils.addMethod(chai.Assertion.prototype, 'foo', function (str) { - * var obj = utils.flag(this, 'object'); - * new chai.Assertion(obj).to.be.equal(str); - * }); - * - * Can also be accessed directly from `chai.Assertion`. - * - * chai.Assertion.addMethod('foo', fn); - * - * Then can be used as any other assertion. - * - * expect(fooStr).to.be.foo('bar'); - * - * @param {Object} ctx object to which the method is added - * @param {String} name of method to add - * @param {Function} method function to be used for name - * @name addMethod - * @api public - */ -var flag = require('chai/lib/chai/utils/flag.js'); - -module.exports = function (ctx, name, method) { - ctx[name] = function () { - var old_ssfi = flag(this, 'ssfi'); - if (old_ssfi && config.includeStack === false) - flag(this, 'ssfi', ctx[name]); - var result = method.apply(this, arguments); - return result === undefined ? this : result; - }; -}; - -}); - -require.register("chai/lib/chai/utils/addProperty.js", function (exports, module) { -/*! - * Chai - addProperty utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ - -/** - * ### addProperty (ctx, name, getter) - * - * Adds a property to the prototype of an object. - * - * utils.addProperty(chai.Assertion.prototype, 'foo', function () { - * var obj = utils.flag(this, 'object'); - * new chai.Assertion(obj).to.be.instanceof(Foo); - * }); - * - * Can also be accessed directly from `chai.Assertion`. - * - * chai.Assertion.addProperty('foo', fn); - * - * Then can be used as any other assertion. - * - * expect(myFoo).to.be.foo; - * - * @param {Object} ctx object to which the property is added - * @param {String} name of property to add - * @param {Function} getter function to be used for name - * @name addProperty - * @api public - */ - -module.exports = function (ctx, name, getter) { - Object.defineProperty(ctx, name, - { get: function () { - var result = getter.call(this); - return result === undefined ? this : result; - } - , configurable: true - }); -}; - -}); - -require.register("chai/lib/chai/utils/flag.js", function (exports, module) { -/*! - * Chai - flag utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ - -/** - * ### flag(object, key, [value]) - * - * Get or set a flag value on an object. If a - * value is provided it will be set, else it will - * return the currently set value or `undefined` if - * the value is not set. - * - * utils.flag(this, 'foo', 'bar'); // setter - * utils.flag(this, 'foo'); // getter, returns `bar` - * - * @param {Object} object constructed Assertion - * @param {String} key - * @param {Mixed} value (optional) - * @name flag - * @api private - */ - -module.exports = function (obj, key, value) { - var flags = obj.__flags || (obj.__flags = Object.create(null)); - if (arguments.length === 3) { - flags[key] = value; - } else { - return flags[key]; - } -}; - -}); - -require.register("chai/lib/chai/utils/getActual.js", function (exports, module) { -/*! - * Chai - getActual utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ - -/** - * # getActual(object, [actual]) - * - * Returns the `actual` value for an Assertion - * - * @param {Object} object (constructed Assertion) - * @param {Arguments} chai.Assertion.prototype.assert arguments - */ - -module.exports = function (obj, args) { - return args.length > 4 ? args[4] : obj._obj; -}; - -}); - -require.register("chai/lib/chai/utils/getEnumerableProperties.js", function (exports, module) { -/*! - * Chai - getEnumerableProperties utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ - -/** - * ### .getEnumerableProperties(object) - * - * This allows the retrieval of enumerable property names of an object, - * inherited or not. - * - * @param {Object} object - * @returns {Array} - * @name getEnumerableProperties - * @api public - */ - -module.exports = function getEnumerableProperties(object) { - var result = []; - for (var name in object) { - result.push(name); - } - return result; -}; - -}); - -require.register("chai/lib/chai/utils/getMessage.js", function (exports, module) { -/*! - * Chai - message composition utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ - -/*! - * Module dependancies - */ - -var flag = require('chai/lib/chai/utils/flag.js') - , getActual = require('chai/lib/chai/utils/getActual.js') - , inspect = require('chai/lib/chai/utils/inspect.js') - , objDisplay = require('chai/lib/chai/utils/objDisplay.js'); - -/** - * ### .getMessage(object, message, negateMessage) - * - * Construct the error message based on flags - * and template tags. Template tags will return - * a stringified inspection of the object referenced. - * - * Message template tags: - * - `#{this}` current asserted object - * - `#{act}` actual value - * - `#{exp}` expected value - * - * @param {Object} object (constructed Assertion) - * @param {Arguments} chai.Assertion.prototype.assert arguments - * @name getMessage - * @api public - */ - -module.exports = function (obj, args) { - var negate = flag(obj, 'negate') - , val = flag(obj, 'object') - , expected = args[3] - , actual = getActual(obj, args) - , msg = negate ? args[2] : args[1] - , flagMsg = flag(obj, 'message'); - - if(typeof msg === "function") msg = msg(); - msg = msg || ''; - msg = msg - .replace(/#{this}/g, objDisplay(val)) - .replace(/#{act}/g, objDisplay(actual)) - .replace(/#{exp}/g, objDisplay(expected)); - - return flagMsg ? flagMsg + ': ' + msg : msg; -}; - -}); - -require.register("chai/lib/chai/utils/getName.js", function (exports, module) { -/*! - * Chai - getName utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ - -/** - * # getName(func) - * - * Gets the name of a function, in a cross-browser way. - * - * @param {Function} a function (usually a constructor) - */ - -module.exports = function (func) { - if (func.name) return func.name; - - var match = /^\s?function ([^(]*)\(/.exec(func); - return match && match[1] ? match[1] : ""; -}; - -}); - -require.register("chai/lib/chai/utils/getPathValue.js", function (exports, module) { -/*! - * Chai - getPathValue utility - * Copyright(c) 2012-2014 Jake Luer - * @see https://github.com/logicalparadox/filtr - * MIT Licensed - */ - -var getPathInfo = require('chai/lib/chai/utils/getPathInfo.js'); - -/** - * ### .getPathValue(path, object) - * - * This allows the retrieval of values in an - * object given a string path. - * - * var obj = { - * prop1: { - * arr: ['a', 'b', 'c'] - * , str: 'Hello' - * } - * , prop2: { - * arr: [ { nested: 'Universe' } ] - * , str: 'Hello again!' - * } - * } - * - * The following would be the results. - * - * getPathValue('prop1.str', obj); // Hello - * getPathValue('prop1.att[2]', obj); // b - * getPathValue('prop2.arr[0].nested', obj); // Universe - * - * @param {String} path - * @param {Object} object - * @returns {Object} value or `undefined` - * @name getPathValue - * @api public - */ -module.exports = function(path, obj) { - var info = getPathInfo(path, obj); - return info.value; -}; - -}); - -require.register("chai/lib/chai/utils/getPathInfo.js", function (exports, module) { -/*! - * Chai - getPathInfo utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ - -var hasProperty = require('chai/lib/chai/utils/hasProperty.js'); - -/** - * ### .getPathInfo(path, object) - * - * This allows the retrieval of property info in an - * object given a string path. - * - * The path info consists of an object with the - * following properties: - * - * * parent - The parent object of the property referenced by `path` - * * name - The name of the final property, a number if it was an array indexer - * * value - The value of the property, if it exists, otherwise `undefined` - * * exists - Whether the property exists or not - * - * @param {String} path - * @param {Object} object - * @returns {Object} info - * @name getPathInfo - * @api public - */ - -module.exports = function getPathInfo(path, obj) { - var parsed = parsePath(path), - last = parsed[parsed.length - 1]; - - var info = { - parent: _getPathValue(parsed, obj, parsed.length - 1), - name: last.p || last.i, - value: _getPathValue(parsed, obj), - }; - info.exists = hasProperty(info.name, info.parent); - - return info; -}; - - -/*! - * ## parsePath(path) - * - * Helper function used to parse string object - * paths. Use in conjunction with `_getPathValue`. - * - * var parsed = parsePath('myobject.property.subprop'); - * - * ### Paths: - * - * * Can be as near infinitely deep and nested - * * Arrays are also valid using the formal `myobject.document[3].property`. - * - * @param {String} path - * @returns {Object} parsed - * @api private - */ - -function parsePath (path) { - var str = path.replace(/\[/g, '.[') - , parts = str.match(/(\\\.|[^.]+?)+/g); - return parts.map(function (value) { - var re = /\[(\d+)\]$/ - , mArr = re.exec(value); - if (mArr) return { i: parseFloat(mArr[1]) }; - else return { p: value }; - }); -} - - -/*! - * ## _getPathValue(parsed, obj) - * - * Helper companion function for `.parsePath` that returns - * the value located at the parsed address. - * - * var value = getPathValue(parsed, obj); - * - * @param {Object} parsed definition from `parsePath`. - * @param {Object} object to search against - * @param {Number} object to search against - * @returns {Object|Undefined} value - * @api private - */ - -function _getPathValue (parsed, obj, index) { - var tmp = obj - , res; - - index = (index === undefined ? parsed.length : index); - - for (var i = 0, l = index; i < l; i++) { - var part = parsed[i]; - if (tmp) { - if ('undefined' !== typeof part.p) - tmp = tmp[part.p]; - else if ('undefined' !== typeof part.i) - tmp = tmp[part.i]; - if (i == (l - 1)) res = tmp; - } else { - res = undefined; - } - } - return res; -} - -}); - -require.register("chai/lib/chai/utils/hasProperty.js", function (exports, module) { -/*! - * Chai - hasProperty utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ - -var type = require('chai/lib/chai/utils/type.js'); - -/** - * ### .hasProperty(object, name) - * - * This allows checking whether an object has - * named property or numeric array index. - * - * Basically does the same thing as the `in` - * operator but works properly with natives - * and null/undefined values. - * - * var obj = { - * arr: ['a', 'b', 'c'] - * , str: 'Hello' - * } - * - * The following would be the results. - * - * hasProperty('str', obj); // true - * hasProperty('constructor', obj); // true - * hasProperty('bar', obj); // false - * - * hasProperty('length', obj.str); // true - * hasProperty(1, obj.str); // true - * hasProperty(5, obj.str); // false - * - * hasProperty('length', obj.arr); // true - * hasProperty(2, obj.arr); // true - * hasProperty(3, obj.arr); // false - * - * @param {Objuect} object - * @param {String|Number} name - * @returns {Boolean} whether it exists - * @name getPathInfo - * @api public - */ - -var literals = { - 'number': Number - , 'string': String -}; - -module.exports = function hasProperty(name, obj) { - var ot = type(obj); - - // Bad Object, obviously no props at all - if(ot === 'null' || ot === 'undefined') - return false; - - // The `in` operator does not work with certain literals - // box these before the check - if(literals[ot] && typeof obj !== 'object') - obj = new literals[ot](obj); - - return name in obj; -}; - -}); - -require.register("chai/lib/chai/utils/getProperties.js", function (exports, module) { -/*! - * Chai - getProperties utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ - -/** - * ### .getProperties(object) - * - * This allows the retrieval of property names of an object, enumerable or not, - * inherited or not. - * - * @param {Object} object - * @returns {Array} - * @name getProperties - * @api public - */ - -module.exports = function getProperties(object) { - var result = Object.getOwnPropertyNames(subject); - - function addProperty(property) { - if (result.indexOf(property) === -1) { - result.push(property); - } - } - - var proto = Object.getPrototypeOf(subject); - while (proto !== null) { - Object.getOwnPropertyNames(proto).forEach(addProperty); - proto = Object.getPrototypeOf(proto); - } - - return result; -}; - -}); - -require.register("chai/lib/chai/utils/index.js", function (exports, module) { -/*! - * chai - * Copyright(c) 2011 Jake Luer - * MIT Licensed - */ - -/*! - * Main exports - */ - -var exports = module.exports = {}; - -/*! - * test utility - */ - -exports.test = require('chai/lib/chai/utils/test.js'); - -/*! - * type utility - */ - -exports.type = require('chai/lib/chai/utils/type.js'); - -/*! - * message utility - */ - -exports.getMessage = require('chai/lib/chai/utils/getMessage.js'); - -/*! - * actual utility - */ - -exports.getActual = require('chai/lib/chai/utils/getActual.js'); - -/*! - * Inspect util - */ - -exports.inspect = require('chai/lib/chai/utils/inspect.js'); - -/*! - * Object Display util - */ - -exports.objDisplay = require('chai/lib/chai/utils/objDisplay.js'); - -/*! - * Flag utility - */ - -exports.flag = require('chai/lib/chai/utils/flag.js'); - -/*! - * Flag transferring utility - */ - -exports.transferFlags = require('chai/lib/chai/utils/transferFlags.js'); - -/*! - * Deep equal utility - */ - -exports.eql = require('chaijs~deep-eql@0.1.3'); - -/*! - * Deep path value - */ - -exports.getPathValue = require('chai/lib/chai/utils/getPathValue.js'); - -/*! - * Deep path info - */ - -exports.getPathInfo = require('chai/lib/chai/utils/getPathInfo.js'); - -/*! - * Check if a property exists - */ - -exports.hasProperty = require('chai/lib/chai/utils/hasProperty.js'); - -/*! - * Function name - */ - -exports.getName = require('chai/lib/chai/utils/getName.js'); - -/*! - * add Property - */ - -exports.addProperty = require('chai/lib/chai/utils/addProperty.js'); - -/*! - * add Method - */ - -exports.addMethod = require('chai/lib/chai/utils/addMethod.js'); - -/*! - * overwrite Property - */ - -exports.overwriteProperty = require('chai/lib/chai/utils/overwriteProperty.js'); - -/*! - * overwrite Method - */ - -exports.overwriteMethod = require('chai/lib/chai/utils/overwriteMethod.js'); - -/*! - * Add a chainable method - */ - -exports.addChainableMethod = require('chai/lib/chai/utils/addChainableMethod.js'); - -/*! - * Overwrite chainable method - */ - -exports.overwriteChainableMethod = require('chai/lib/chai/utils/overwriteChainableMethod.js'); - - -}); - -require.register("chai/lib/chai/utils/inspect.js", function (exports, module) { -// This is (almost) directly from Node.js utils -// https://github.com/joyent/node/blob/f8c335d0caf47f16d31413f89aa28eda3878e3aa/lib/util.js - -var getName = require('chai/lib/chai/utils/getName.js'); -var getProperties = require('chai/lib/chai/utils/getProperties.js'); -var getEnumerableProperties = require('chai/lib/chai/utils/getEnumerableProperties.js'); - -module.exports = inspect; - -/** - * Echos the value of a value. Trys to print the value out - * in the best way possible given the different types. - * - * @param {Object} obj The object to print out. - * @param {Boolean} showHidden Flag that shows hidden (not enumerable) - * properties of objects. - * @param {Number} depth Depth in which to descend in object. Default is 2. - * @param {Boolean} colors Flag to turn on ANSI escape codes to color the - * output. Default is false (no coloring). - */ -function inspect(obj, showHidden, depth, colors) { - var ctx = { - showHidden: showHidden, - seen: [], - stylize: function (str) { return str; } - }; - return formatValue(ctx, obj, (typeof depth === 'undefined' ? 2 : depth)); -} - -// Returns true if object is a DOM element. -var isDOMElement = function (object) { - if (typeof HTMLElement === 'object') { - return object instanceof HTMLElement; - } else { - return object && - typeof object === 'object' && - object.nodeType === 1 && - typeof object.nodeName === 'string'; - } -}; - -function formatValue(ctx, value, recurseTimes) { - // Provide a hook for user-specified inspect functions. - // Check that value is an object with an inspect function on it - if (value && typeof value.inspect === 'function' && - // Filter out the util module, it's inspect function is special - value.inspect !== exports.inspect && - // Also filter out any prototype objects using the circular check. - !(value.constructor && value.constructor.prototype === value)) { - var ret = value.inspect(recurseTimes); - if (typeof ret !== 'string') { - ret = formatValue(ctx, ret, recurseTimes); - } - return ret; - } - - // Primitive types cannot have properties - var primitive = formatPrimitive(ctx, value); - if (primitive) { - return primitive; - } - - // If this is a DOM element, try to get the outer HTML. - if (isDOMElement(value)) { - if ('outerHTML' in value) { - return value.outerHTML; - // This value does not have an outerHTML attribute, - // it could still be an XML element - } else { - // Attempt to serialize it - try { - if (document.xmlVersion) { - var xmlSerializer = new XMLSerializer(); - return xmlSerializer.serializeToString(value); - } else { - // Firefox 11- do not support outerHTML - // It does, however, support innerHTML - // Use the following to render the element - var ns = "http://www.w3.org/1999/xhtml"; - var container = document.createElementNS(ns, '_'); - - container.appendChild(value.cloneNode(false)); - html = container.innerHTML - .replace('><', '>' + value.innerHTML + '<'); - container.innerHTML = ''; - return html; - } - } catch (err) { - // This could be a non-native DOM implementation, - // continue with the normal flow: - // printing the element as if it is an object. - } - } - } - - // Look up the keys of the object. - var visibleKeys = getEnumerableProperties(value); - var keys = ctx.showHidden ? getProperties(value) : visibleKeys; - - // Some type of object without properties can be shortcutted. - // In IE, errors have a single `stack` property, or if they are vanilla `Error`, - // a `stack` plus `description` property; ignore those for consistency. - if (keys.length === 0 || (isError(value) && ( - (keys.length === 1 && keys[0] === 'stack') || - (keys.length === 2 && keys[0] === 'description' && keys[1] === 'stack') - ))) { - if (typeof value === 'function') { - var name = getName(value); - var nameSuffix = name ? ': ' + name : ''; - return ctx.stylize('[Function' + nameSuffix + ']', 'special'); - } - if (isRegExp(value)) { - return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); - } - if (isDate(value)) { - return ctx.stylize(Date.prototype.toUTCString.call(value), 'date'); - } - if (isError(value)) { - return formatError(value); - } - } - - var base = '', array = false, braces = ['{', '}']; - - // Make Array say that they are Array - if (isArray(value)) { - array = true; - braces = ['[', ']']; - } - - // Make functions say that they are functions - if (typeof value === 'function') { - var name = getName(value); - var nameSuffix = name ? ': ' + name : ''; - base = ' [Function' + nameSuffix + ']'; - } - - // Make RegExps say that they are RegExps - if (isRegExp(value)) { - base = ' ' + RegExp.prototype.toString.call(value); - } - - // Make dates with properties first say the date - if (isDate(value)) { - base = ' ' + Date.prototype.toUTCString.call(value); - } - - // Make error with message first say the error - if (isError(value)) { - return formatError(value); - } - - if (keys.length === 0 && (!array || value.length == 0)) { - return braces[0] + base + braces[1]; - } - - if (recurseTimes < 0) { - if (isRegExp(value)) { - return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); - } else { - return ctx.stylize('[Object]', 'special'); - } - } - - ctx.seen.push(value); - - var output; - if (array) { - output = formatArray(ctx, value, recurseTimes, visibleKeys, keys); - } else { - output = keys.map(function(key) { - return formatProperty(ctx, value, recurseTimes, visibleKeys, key, array); - }); - } - - ctx.seen.pop(); - - return reduceToSingleString(output, base, braces); -} - - -function formatPrimitive(ctx, value) { - switch (typeof value) { - case 'undefined': - return ctx.stylize('undefined', 'undefined'); - - case 'string': - var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '') - .replace(/'/g, "\\'") - .replace(/\\"/g, '"') + '\''; - return ctx.stylize(simple, 'string'); - - case 'number': - if (value === 0 && (1/value) === -Infinity) { - return ctx.stylize('-0', 'number'); - } - return ctx.stylize('' + value, 'number'); - - case 'boolean': - return ctx.stylize('' + value, 'boolean'); - } - // For some reason typeof null is "object", so special case here. - if (value === null) { - return ctx.stylize('null', 'null'); - } -} - - -function formatError(value) { - return '[' + Error.prototype.toString.call(value) + ']'; -} - - -function formatArray(ctx, value, recurseTimes, visibleKeys, keys) { - var output = []; - for (var i = 0, l = value.length; i < l; ++i) { - if (Object.prototype.hasOwnProperty.call(value, String(i))) { - output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, - String(i), true)); - } else { - output.push(''); - } - } - keys.forEach(function(key) { - if (!key.match(/^\d+$/)) { - output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, - key, true)); - } - }); - return output; -} - - -function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) { - var name, str; - if (value.__lookupGetter__) { - if (value.__lookupGetter__(key)) { - if (value.__lookupSetter__(key)) { - str = ctx.stylize('[Getter/Setter]', 'special'); - } else { - str = ctx.stylize('[Getter]', 'special'); - } - } else { - if (value.__lookupSetter__(key)) { - str = ctx.stylize('[Setter]', 'special'); - } - } - } - if (visibleKeys.indexOf(key) < 0) { - name = '[' + key + ']'; - } - if (!str) { - if (ctx.seen.indexOf(value[key]) < 0) { - if (recurseTimes === null) { - str = formatValue(ctx, value[key], null); - } else { - str = formatValue(ctx, value[key], recurseTimes - 1); - } - if (str.indexOf('\n') > -1) { - if (array) { - str = str.split('\n').map(function(line) { - return ' ' + line; - }).join('\n').substr(2); - } else { - str = '\n' + str.split('\n').map(function(line) { - return ' ' + line; - }).join('\n'); - } - } - } else { - str = ctx.stylize('[Circular]', 'special'); - } - } - if (typeof name === 'undefined') { - if (array && key.match(/^\d+$/)) { - return str; - } - name = JSON.stringify('' + key); - if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) { - name = name.substr(1, name.length - 2); - name = ctx.stylize(name, 'name'); - } else { - name = name.replace(/'/g, "\\'") - .replace(/\\"/g, '"') - .replace(/(^"|"$)/g, "'"); - name = ctx.stylize(name, 'string'); - } - } - - return name + ': ' + str; -} - - -function reduceToSingleString(output, base, braces) { - var numLinesEst = 0; - var length = output.reduce(function(prev, cur) { - numLinesEst++; - if (cur.indexOf('\n') >= 0) numLinesEst++; - return prev + cur.length + 1; - }, 0); - - if (length > 60) { - return braces[0] + - (base === '' ? '' : base + '\n ') + - ' ' + - output.join(',\n ') + - ' ' + - braces[1]; - } - - return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1]; -} - -function isArray(ar) { - return Array.isArray(ar) || - (typeof ar === 'object' && objectToString(ar) === '[object Array]'); -} - -function isRegExp(re) { - return typeof re === 'object' && objectToString(re) === '[object RegExp]'; -} - -function isDate(d) { - return typeof d === 'object' && objectToString(d) === '[object Date]'; -} - -function isError(e) { - return typeof e === 'object' && objectToString(e) === '[object Error]'; -} - -function objectToString(o) { - return Object.prototype.toString.call(o); -} - -}); - -require.register("chai/lib/chai/utils/objDisplay.js", function (exports, module) { -/*! - * Chai - flag utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ - -/*! - * Module dependancies - */ - -var inspect = require('chai/lib/chai/utils/inspect.js'); -var config = require('chai/lib/chai/config.js'); - -/** - * ### .objDisplay (object) - * - * Determines if an object or an array matches - * criteria to be inspected in-line for error - * messages or should be truncated. - * - * @param {Mixed} javascript object to inspect - * @name objDisplay - * @api public - */ - -module.exports = function (obj) { - var str = inspect(obj) - , type = Object.prototype.toString.call(obj); - - if (config.truncateThreshold && str.length >= config.truncateThreshold) { - if (type === '[object Function]') { - return !obj.name || obj.name === '' - ? '[Function]' - : '[Function: ' + obj.name + ']'; - } else if (type === '[object Array]') { - return '[ Array(' + obj.length + ') ]'; - } else if (type === '[object Object]') { - var keys = Object.keys(obj) - , kstr = keys.length > 2 - ? keys.splice(0, 2).join(', ') + ', ...' - : keys.join(', '); - return '{ Object (' + kstr + ') }'; - } else { - return str; - } - } else { - return str; - } -}; - -}); - -require.register("chai/lib/chai/utils/overwriteMethod.js", function (exports, module) { -/*! - * Chai - overwriteMethod utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ - -/** - * ### overwriteMethod (ctx, name, fn) - * - * Overwites an already existing method and provides - * access to previous function. Must return function - * to be used for name. - * - * utils.overwriteMethod(chai.Assertion.prototype, 'equal', function (_super) { - * return function (str) { - * var obj = utils.flag(this, 'object'); - * if (obj instanceof Foo) { - * new chai.Assertion(obj.value).to.equal(str); - * } else { - * _super.apply(this, arguments); - * } - * } - * }); - * - * Can also be accessed directly from `chai.Assertion`. - * - * chai.Assertion.overwriteMethod('foo', fn); - * - * Then can be used as any other assertion. - * - * expect(myFoo).to.equal('bar'); - * - * @param {Object} ctx object whose method is to be overwritten - * @param {String} name of method to overwrite - * @param {Function} method function that returns a function to be used for name - * @name overwriteMethod - * @api public - */ - -module.exports = function (ctx, name, method) { - var _method = ctx[name] - , _super = function () { return this; }; - - if (_method && 'function' === typeof _method) - _super = _method; - - ctx[name] = function () { - var result = method(_super).apply(this, arguments); - return result === undefined ? this : result; - } -}; - -}); - -require.register("chai/lib/chai/utils/overwriteProperty.js", function (exports, module) { -/*! - * Chai - overwriteProperty utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ - -/** - * ### overwriteProperty (ctx, name, fn) - * - * Overwites an already existing property getter and provides - * access to previous value. Must return function to use as getter. - * - * utils.overwriteProperty(chai.Assertion.prototype, 'ok', function (_super) { - * return function () { - * var obj = utils.flag(this, 'object'); - * if (obj instanceof Foo) { - * new chai.Assertion(obj.name).to.equal('bar'); - * } else { - * _super.call(this); - * } - * } - * }); - * - * - * Can also be accessed directly from `chai.Assertion`. - * - * chai.Assertion.overwriteProperty('foo', fn); - * - * Then can be used as any other assertion. - * - * expect(myFoo).to.be.ok; - * - * @param {Object} ctx object whose property is to be overwritten - * @param {String} name of property to overwrite - * @param {Function} getter function that returns a getter function to be used for name - * @name overwriteProperty - * @api public - */ - -module.exports = function (ctx, name, getter) { - var _get = Object.getOwnPropertyDescriptor(ctx, name) - , _super = function () {}; - - if (_get && 'function' === typeof _get.get) - _super = _get.get - - Object.defineProperty(ctx, name, - { get: function () { - var result = getter(_super).call(this); - return result === undefined ? this : result; - } - , configurable: true - }); -}; - -}); - -require.register("chai/lib/chai/utils/overwriteChainableMethod.js", function (exports, module) { -/*! - * Chai - overwriteChainableMethod utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ - -/** - * ### overwriteChainableMethod (ctx, name, method, chainingBehavior) - * - * Overwites an already existing chainable method - * and provides access to the previous function or - * property. Must return functions to be used for - * name. - * - * utils.overwriteChainableMethod(chai.Assertion.prototype, 'length', - * function (_super) { - * } - * , function (_super) { - * } - * ); - * - * Can also be accessed directly from `chai.Assertion`. - * - * chai.Assertion.overwriteChainableMethod('foo', fn, fn); - * - * Then can be used as any other assertion. - * - * expect(myFoo).to.have.length(3); - * expect(myFoo).to.have.length.above(3); - * - * @param {Object} ctx object whose method / property is to be overwritten - * @param {String} name of method / property to overwrite - * @param {Function} method function that returns a function to be used for name - * @param {Function} chainingBehavior function that returns a function to be used for property - * @name overwriteChainableMethod - * @api public - */ - -module.exports = function (ctx, name, method, chainingBehavior) { - var chainableBehavior = ctx.__methods[name]; - - var _chainingBehavior = chainableBehavior.chainingBehavior; - chainableBehavior.chainingBehavior = function () { - var result = chainingBehavior(_chainingBehavior).call(this); - return result === undefined ? this : result; - }; - - var _method = chainableBehavior.method; - chainableBehavior.method = function () { - var result = method(_method).apply(this, arguments); - return result === undefined ? this : result; - }; -}; - -}); - -require.register("chai/lib/chai/utils/test.js", function (exports, module) { -/*! - * Chai - test utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ - -/*! - * Module dependancies - */ - -var flag = require('chai/lib/chai/utils/flag.js'); - -/** - * # test(object, expression) - * - * Test and object for expression. - * - * @param {Object} object (constructed Assertion) - * @param {Arguments} chai.Assertion.prototype.assert arguments - */ - -module.exports = function (obj, args) { - var negate = flag(obj, 'negate') - , expr = args[0]; - return negate ? !expr : expr; -}; - -}); - -require.register("chai/lib/chai/utils/transferFlags.js", function (exports, module) { -/*! - * Chai - transferFlags utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ - -/** - * ### transferFlags(assertion, object, includeAll = true) - * - * Transfer all the flags for `assertion` to `object`. If - * `includeAll` is set to `false`, then the base Chai - * assertion flags (namely `object`, `ssfi`, and `message`) - * will not be transferred. - * - * - * var newAssertion = new Assertion(); - * utils.transferFlags(assertion, newAssertion); - * - * var anotherAsseriton = new Assertion(myObj); - * utils.transferFlags(assertion, anotherAssertion, false); - * - * @param {Assertion} assertion the assertion to transfer the flags from - * @param {Object} object the object to transfer the flags to; usually a new assertion - * @param {Boolean} includeAll - * @name transferFlags - * @api private - */ - -module.exports = function (assertion, object, includeAll) { - var flags = assertion.__flags || (assertion.__flags = Object.create(null)); - - if (!object.__flags) { - object.__flags = Object.create(null); - } - - includeAll = arguments.length === 3 ? includeAll : true; - - for (var flag in flags) { - if (includeAll || - (flag !== 'object' && flag !== 'ssfi' && flag != 'message')) { - object.__flags[flag] = flags[flag]; - } - } -}; - -}); - -require.register("chai/lib/chai/utils/type.js", function (exports, module) { -/*! - * Chai - type utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ - -/*! - * Detectable javascript natives - */ - -var natives = { - '[object Arguments]': 'arguments' - , '[object Array]': 'array' - , '[object Date]': 'date' - , '[object Function]': 'function' - , '[object Number]': 'number' - , '[object RegExp]': 'regexp' - , '[object String]': 'string' -}; - -/** - * ### type(object) - * - * Better implementation of `typeof` detection that can - * be used cross-browser. Handles the inconsistencies of - * Array, `null`, and `undefined` detection. - * - * utils.type({}) // 'object' - * utils.type(null) // `null' - * utils.type(undefined) // `undefined` - * utils.type([]) // `array` - * - * @param {Mixed} object to detect type of - * @name type - * @api private - */ - -module.exports = function (obj) { - var str = Object.prototype.toString.call(obj); - if (natives[str]) return natives[str]; - if (obj === null) return 'null'; - if (obj === undefined) return 'undefined'; - if (obj === Object(obj)) return 'object'; - return typeof obj; -}; - -}); - -if (typeof exports == "object") { - module.exports = require("chai"); -} else if (typeof define == "function" && define.amd) { - define("chai", [], function(){ return require("chai"); }); -} else { - (this || window)["chai"] = require("chai"); -} -})() \ No newline at end of file diff --git a/test/lib/mocha.js b/test/lib/mocha.js deleted file mode 100755 index 061ee3cf52b..00000000000 --- a/test/lib/mocha.js +++ /dev/null @@ -1,4597 +0,0 @@ -;(function(){ - - -// CommonJS require() - -function require(p){ - var path = require.resolve(p) - , mod = require.modules[path]; - if (!mod) throw new Error('failed to require "' + p + '"'); - if (!mod.exports) { - mod.exports = {}; - mod.call(mod.exports, mod, mod.exports, require.relative(path)); - } - return mod.exports; - } - -require.modules = {}; - -require.resolve = function (path){ - var orig = path - , reg = path + '.js' - , index = path + '/index.js'; - return require.modules[reg] && reg - || require.modules[index] && index - || orig; - }; - -require.register = function (path, fn){ - require.modules[path] = fn; - }; - -require.relative = function (parent) { - return function(p){ - if ('.' != p.charAt(0)) return require(p); - - var path = parent.split('/') - , segs = p.split('/'); - path.pop(); - - for (var i = 0; i < segs.length; i++) { - var seg = segs[i]; - if ('..' == seg) path.pop(); - else if ('.' != seg) path.push(seg); - } - - return require(path.join('/')); - }; - }; - - -require.register("browser/debug.js", function(module, exports, require){ - -module.exports = function(type){ - return function(){ - - } -}; -}); // module: browser/debug.js - -require.register("browser/diff.js", function(module, exports, require){ - -}); // module: browser/diff.js - -require.register("browser/events.js", function(module, exports, require){ - -/** - * Module exports. - */ - -exports.EventEmitter = EventEmitter; - -/** - * Check if `obj` is an array. - */ - -function isArray(obj) { - return '[object Array]' == {}.toString.call(obj); -} - -/** - * Event emitter constructor. - * - * @api public - */ - -function EventEmitter(){}; - -/** - * Adds a listener. - * - * @api public - */ - -EventEmitter.prototype.on = function (name, fn) { - if (!this.$events) { - this.$events = {}; - } - - if (!this.$events[name]) { - this.$events[name] = fn; - } else if (isArray(this.$events[name])) { - this.$events[name].push(fn); - } else { - this.$events[name] = [this.$events[name], fn]; - } - - return this; -}; - -EventEmitter.prototype.addListener = EventEmitter.prototype.on; - -/** - * Adds a volatile listener. - * - * @api public - */ - -EventEmitter.prototype.once = function (name, fn) { - var self = this; - - function on () { - self.removeListener(name, on); - fn.apply(this, arguments); - }; - - on.listener = fn; - this.on(name, on); - - return this; -}; - -/** - * Removes a listener. - * - * @api public - */ - -EventEmitter.prototype.removeListener = function (name, fn) { - if (this.$events && this.$events[name]) { - var list = this.$events[name]; - - if (isArray(list)) { - var pos = -1; - - for (var i = 0, l = list.length; i < l; i++) { - if (list[i] === fn || (list[i].listener && list[i].listener === fn)) { - pos = i; - break; - } - } - - if (pos < 0) { - return this; - } - - list.splice(pos, 1); - - if (!list.length) { - delete this.$events[name]; - } - } else if (list === fn || (list.listener && list.listener === fn)) { - delete this.$events[name]; - } - } - - return this; -}; - -/** - * Removes all listeners for an event. - * - * @api public - */ - -EventEmitter.prototype.removeAllListeners = function (name) { - if (name === undefined) { - this.$events = {}; - return this; - } - - if (this.$events && this.$events[name]) { - this.$events[name] = null; - } - - return this; -}; - -/** - * Gets all listeners for a certain event. - * - * @api public - */ - -EventEmitter.prototype.listeners = function (name) { - if (!this.$events) { - this.$events = {}; - } - - if (!this.$events[name]) { - this.$events[name] = []; - } - - if (!isArray(this.$events[name])) { - this.$events[name] = [this.$events[name]]; - } - - return this.$events[name]; -}; - -/** - * Emits an event. - * - * @api public - */ - -EventEmitter.prototype.emit = function (name) { - if (!this.$events) { - return false; - } - - var handler = this.$events[name]; - - if (!handler) { - return false; - } - - var args = [].slice.call(arguments, 1); - - if ('function' == typeof handler) { - handler.apply(this, args); - } else if (isArray(handler)) { - var listeners = handler.slice(); - - for (var i = 0, l = listeners.length; i < l; i++) { - listeners[i].apply(this, args); - } - } else { - return false; - } - - return true; -}; -}); // module: browser/events.js - -require.register("browser/fs.js", function(module, exports, require){ - -}); // module: browser/fs.js - -require.register("browser/path.js", function(module, exports, require){ - -}); // module: browser/path.js - -require.register("browser/progress.js", function(module, exports, require){ - -/** - * Expose `Progress`. - */ - -module.exports = Progress; - -/** - * Initialize a new `Progress` indicator. - */ - -function Progress() { - this.percent = 0; - this.size(0); - this.fontSize(11); - this.font('helvetica, arial, sans-serif'); -} - -/** - * Set progress size to `n`. - * - * @param {Number} n - * @return {Progress} for chaining - * @api public - */ - -Progress.prototype.size = function(n){ - this._size = n; - return this; -}; - -/** - * Set text to `str`. - * - * @param {String} str - * @return {Progress} for chaining - * @api public - */ - -Progress.prototype.text = function(str){ - this._text = str; - return this; -}; - -/** - * Set font size to `n`. - * - * @param {Number} n - * @return {Progress} for chaining - * @api public - */ - -Progress.prototype.fontSize = function(n){ - this._fontSize = n; - return this; -}; - -/** - * Set font `family`. - * - * @param {String} family - * @return {Progress} for chaining - */ - -Progress.prototype.font = function(family){ - this._font = family; - return this; -}; - -/** - * Update percentage to `n`. - * - * @param {Number} n - * @return {Progress} for chaining - */ - -Progress.prototype.update = function(n){ - this.percent = n; - return this; -}; - -/** - * Draw on `ctx`. - * - * @param {CanvasRenderingContext2d} ctx - * @return {Progress} for chaining - */ - -Progress.prototype.draw = function(ctx){ - var percent = Math.min(this.percent, 100) - , size = this._size - , half = size / 2 - , x = half - , y = half - , rad = half - 1 - , fontSize = this._fontSize; - - ctx.font = fontSize + 'px ' + this._font; - - var angle = Math.PI * 2 * (percent / 100); - ctx.clearRect(0, 0, size, size); - - // outer circle - ctx.strokeStyle = '#9f9f9f'; - ctx.beginPath(); - ctx.arc(x, y, rad, 0, angle, false); - ctx.stroke(); - - // inner circle - ctx.strokeStyle = '#eee'; - ctx.beginPath(); - ctx.arc(x, y, rad - 1, 0, angle, true); - ctx.stroke(); - - // text - var text = this._text || (percent | 0) + '%' - , w = ctx.measureText(text).width; - - ctx.fillText( - text - , x - w / 2 + 1 - , y + fontSize / 2 - 1); - - return this; -}; - -}); // module: browser/progress.js - -require.register("browser/tty.js", function(module, exports, require){ - -exports.isatty = function(){ - return true; -}; - -exports.getWindowSize = function(){ - return [window.innerHeight, window.innerWidth]; -}; -}); // module: browser/tty.js - -require.register("context.js", function(module, exports, require){ - -/** - * Expose `Context`. - */ - -module.exports = Context; - -/** - * Initialize a new `Context`. - * - * @api private - */ - -function Context(){} - -/** - * Set or get the context `Runnable` to `runnable`. - * - * @param {Runnable} runnable - * @return {Context} - * @api private - */ - -Context.prototype.runnable = function(runnable){ - if (0 == arguments.length) return this._runnable; - this.test = this._runnable = runnable; - return this; -}; - -/** - * Set test timeout `ms`. - * - * @param {Number} ms - * @return {Context} self - * @api private - */ - -Context.prototype.timeout = function(ms){ - this.runnable().timeout(ms); - return this; -}; - -/** - * Inspect the context void of `._runnable`. - * - * @return {String} - * @api private - */ - -Context.prototype.inspect = function(){ - return JSON.stringify(this, function(key, val){ - if ('_runnable' == key) return; - if ('test' == key) return; - return val; - }, 2); -}; - -}); // module: context.js - -require.register("hook.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Runnable = require('./runnable'); - -/** - * Expose `Hook`. - */ - -module.exports = Hook; - -/** - * Initialize a new `Hook` with the given `title` and callback `fn`. - * - * @param {String} title - * @param {Function} fn - * @api private - */ - -function Hook(title, fn) { - Runnable.call(this, title, fn); - this.type = 'hook'; -} - -/** - * Inherit from `Runnable.prototype`. - */ - -Hook.prototype = new Runnable; -Hook.prototype.constructor = Hook; - - -/** - * Get or set the test `err`. - * - * @param {Error} err - * @return {Error} - * @api public - */ - -Hook.prototype.error = function(err){ - if (0 == arguments.length) { - var err = this._error; - this._error = null; - return err; - } - - this._error = err; -}; - - -}); // module: hook.js - -require.register("interfaces/bdd.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Suite = require('../suite') - , Test = require('../test'); - -/** - * BDD-style interface: - * - * describe('Array', function(){ - * describe('#indexOf()', function(){ - * it('should return -1 when not present', function(){ - * - * }); - * - * it('should return the index when present', function(){ - * - * }); - * }); - * }); - * - */ - -module.exports = function(suite){ - var suites = [suite]; - - suite.on('pre-require', function(context){ - - /** - * Execute before running tests. - */ - - context.before = function(fn){ - suites[0].beforeAll(fn); - }; - - /** - * Execute after running tests. - */ - - context.after = function(fn){ - suites[0].afterAll(fn); - }; - - /** - * Execute before each test case. - */ - - context.beforeEach = function(fn){ - suites[0].beforeEach(fn); - }; - - /** - * Execute after each test case. - */ - - context.afterEach = function(fn){ - suites[0].afterEach(fn); - }; - - /** - * Pending describe. - */ - - context.xdescribe = context.xcontext = function(title, fn){ - var suite = Suite.create(suites[0], title); - suite.pending = true; - suites.unshift(suite); - fn(); - suites.shift(); - }; - - /** - * Describe a "suite" with the given `title` - * and callback `fn` containing nested suites - * and/or tests. - */ - - context.describe = context.context = function(title, fn){ - var suite = Suite.create(suites[0], title); - suites.unshift(suite); - fn(); - suites.shift(); - }; - - /** - * Describe a specification or test-case - * with the given `title` and callback `fn` - * acting as a thunk. - */ - - context.it = context.specify = function(title, fn){ - var suite = suites[0]; - if (suite.pending) var fn = null; - suite.addTest(new Test(title, fn)); - }; - - /** - * Pending test case. - */ - - context.xit = context.xspecify = function(title){ - context.it(title); - }; - }); -}; - -}); // module: interfaces/bdd.js - -require.register("interfaces/exports.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Suite = require('../suite') - , Test = require('../test'); - -/** - * TDD-style interface: - * - * exports.Array = { - * '#indexOf()': { - * 'should return -1 when the value is not present': function(){ - * - * }, - * - * 'should return the correct index when the value is present': function(){ - * - * } - * } - * }; - * - */ - -module.exports = function(suite){ - var suites = [suite]; - - suite.on('require', visit); - - function visit(obj) { - var suite; - for (var key in obj) { - if ('function' == typeof obj[key]) { - var fn = obj[key]; - switch (key) { - case 'before': - suites[0].beforeAll(fn); - break; - case 'after': - suites[0].afterAll(fn); - break; - case 'beforeEach': - suites[0].beforeEach(fn); - break; - case 'afterEach': - suites[0].afterEach(fn); - break; - default: - suites[0].addTest(new Test(key, fn)); - } - } else { - var suite = Suite.create(suites[0], key); - suites.unshift(suite); - visit(obj[key]); - suites.shift(); - } - } - } -}; -}); // module: interfaces/exports.js - -require.register("interfaces/index.js", function(module, exports, require){ - -exports.bdd = require('./bdd'); -exports.tdd = require('./tdd'); -exports.qunit = require('./qunit'); -exports.exports = require('./exports'); - -}); // module: interfaces/index.js - -require.register("interfaces/qunit.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Suite = require('../suite') - , Test = require('../test'); - -/** - * QUnit-style interface: - * - * suite('Array'); - * - * test('#length', function(){ - * var arr = [1,2,3]; - * ok(arr.length == 3); - * }); - * - * test('#indexOf()', function(){ - * var arr = [1,2,3]; - * ok(arr.indexOf(1) == 0); - * ok(arr.indexOf(2) == 1); - * ok(arr.indexOf(3) == 2); - * }); - * - * suite('String'); - * - * test('#length', function(){ - * ok('foo'.length == 3); - * }); - * - */ - -module.exports = function(suite){ - var suites = [suite]; - - suite.on('pre-require', function(context){ - - /** - * Execute before running tests. - */ - - context.before = function(fn){ - suites[0].beforeAll(fn); - }; - - /** - * Execute after running tests. - */ - - context.after = function(fn){ - suites[0].afterAll(fn); - }; - - /** - * Execute before each test case. - */ - - context.beforeEach = function(fn){ - suites[0].beforeEach(fn); - }; - - /** - * Execute after each test case. - */ - - context.afterEach = function(fn){ - suites[0].afterEach(fn); - }; - - /** - * Describe a "suite" with the given `title`. - */ - - context.suite = function(title){ - if (suites.length > 1) suites.shift(); - var suite = Suite.create(suites[0], title); - suites.unshift(suite); - }; - - /** - * Describe a specification or test-case - * with the given `title` and callback `fn` - * acting as a thunk. - */ - - context.test = function(title, fn){ - suites[0].addTest(new Test(title, fn)); - }; - }); -}; - -}); // module: interfaces/qunit.js - -require.register("interfaces/tdd.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Suite = require('../suite') - , Test = require('../test'); - -/** - * TDD-style interface: - * - * suite('Array', function(){ - * suite('#indexOf()', function(){ - * suiteSetup(function(){ - * - * }); - * - * test('should return -1 when not present', function(){ - * - * }); - * - * test('should return the index when present', function(){ - * - * }); - * - * suiteTeardown(function(){ - * - * }); - * }); - * }); - * - */ - -module.exports = function(suite){ - var suites = [suite]; - - suite.on('pre-require', function(context){ - - /** - * Execute before each test case. - */ - - context.setup = function(fn){ - suites[0].beforeEach(fn); - }; - - /** - * Execute after each test case. - */ - - context.teardown = function(fn){ - suites[0].afterEach(fn); - }; - - /** - * Execute before the suite. - */ - - context.suiteSetup = function(fn){ - suites[0].beforeAll(fn); - }; - - /** - * Execute after the suite. - */ - - context.suiteTeardown = function(fn){ - suites[0].afterAll(fn); - }; - - /** - * Describe a "suite" with the given `title` - * and callback `fn` containing nested suites - * and/or tests. - */ - - context.suite = function(title, fn){ - var suite = Suite.create(suites[0], title); - suites.unshift(suite); - fn(); - suites.shift(); - }; - - /** - * Describe a specification or test-case - * with the given `title` and callback `fn` - * acting as a thunk. - */ - - context.test = function(title, fn){ - suites[0].addTest(new Test(title, fn)); - }; - }); -}; - -}); // module: interfaces/tdd.js - -require.register("mocha.js", function(module, exports, require){ -/*! - * mocha - * Copyright(c) 2011 TJ Holowaychuk - * MIT Licensed - */ - -/** - * Module dependencies. - */ - -var path = require('browser/path'); - -/** - * Expose `Mocha`. - */ - -exports = module.exports = Mocha; - -/** - * Expose internals. - */ - -exports.utils = require('./utils'); -exports.interfaces = require('./interfaces'); -exports.reporters = require('./reporters'); -exports.Runnable = require('./runnable'); -exports.Context = require('./context'); -exports.Runner = require('./runner'); -exports.Suite = require('./suite'); -exports.Hook = require('./hook'); -exports.Test = require('./test'); - -/** - * Return image `name` path. - * - * @param {String} name - * @return {String} - * @api private - */ - -function image(name) { - return __dirname + '/../images/' + name + '.png'; -} - -/** - * Setup mocha with `options`. - * - * Options: - * - * - `ui` name "bdd", "tdd", "exports" etc - * - `reporter` reporter instance, defaults to `mocha.reporters.Dot` - * - `globals` array of accepted globals - * - `timeout` timeout in milliseconds - * - `ignoreLeaks` ignore global leaks - * - `grep` string or regexp to filter tests with - * - * @param {Object} options - * @api public - */ - -function Mocha(options) { - options = options || {}; - this.files = []; - this.options = options; - this.grep(options.grep); - this.suite = new exports.Suite('', new exports.Context); - this.ui(options.ui); - this.reporter(options.reporter); - if (options.timeout) this.suite.timeout(options.timeout); -} - -/** - * Add test `file`. - * - * @param {String} file - * @api public - */ - -Mocha.prototype.addFile = function(file){ - this.files.push(file); - return this; -}; - -/** - * Set reporter to `name`, defaults to "dot". - * - * @param {String} name - * @api public - */ - -Mocha.prototype.reporter = function(name){ - name = name || 'dot'; - this._reporter = require('./reporters/' + name); - if (!this._reporter) throw new Error('invalid reporter "' + name + '"'); - return this; -}; - -/** - * Set test UI `name`, defaults to "bdd". - * - * @param {String} bdd - * @api public - */ - -Mocha.prototype.ui = function(name){ - name = name || 'bdd'; - this._ui = exports.interfaces[name]; - if (!this._ui) throw new Error('invalid interface "' + name + '"'); - this._ui = this._ui(this.suite); - return this; -}; - -/** - * Load registered files. - * - * @api private - */ - -Mocha.prototype.loadFiles = function(fn){ - var suite = this.suite; - var pending = this.files.length; - this.files.forEach(function(file){ - file = path.resolve(file); - suite.emit('pre-require', global, file); - suite.emit('require', require(file), file); - suite.emit('post-require', global, file); - --pending || (fn && fn()); - }); -}; - -/** - * Enable growl support. - * - * @api private - */ - -Mocha.prototype._growl = function(runner, reporter) { - var notify = require('growl'); - - runner.on('end', function(){ - var stats = reporter.stats; - if (stats.failures) { - var msg = stats.failures + ' of ' + runner.total + ' tests failed'; - notify(msg, { name: 'mocha', title: 'Failed', image: image('error') }); - } else { - notify(stats.passes + ' tests passed in ' + stats.duration + 'ms', { - name: 'mocha' - , title: 'Passed' - , image: image('ok') - }); - } - }); -}; - -/** - * Add regexp to grep for to the options object - * - * @param {RegExp} or {String} re - * @return {Mocha} - * @api public - */ - -Mocha.prototype.grep = function(re){ - this.options.grep = 'string' == typeof re - ? new RegExp(re) - : re; - return this; -}; - -/** - * Invert `.grep()` matches. - * - * @return {Mocha} - * @api public - */ - -Mocha.prototype.invert = function(){ - this.options.invert = true; - return this; -}; - -/** - * Ignore global leaks. - * - * @return {Mocha} - * @api public - */ - -Mocha.prototype.ignoreLeaks = function(){ - this.options.ignoreLeaks = true; - return this; -}; - -/** - * Enable growl support. - * - * @return {Mocha} - * @api public - */ - -Mocha.prototype.growl = function(){ - this.options.growl = true; - return this; -}; - -/** - * Ignore `globals`. - * - * @param {Array} globals - * @return {Mocha} - * @api public - */ - -Mocha.prototype.globals = function(globals){ - this.options.globals = globals; - return this; -}; - -/** - * Run tests and invoke `fn()` when complete. - * - * @param {Function} fn - * @return {Runner} - * @api public - */ - -Mocha.prototype.run = function(fn){ - this.loadFiles(); - var suite = this.suite; - var options = this.options; - var runner = new exports.Runner(suite); - var reporter = new this._reporter(runner); - runner.ignoreLeaks = options.ignoreLeaks; - if (options.grep) runner.grep(options.grep, options.invert); - if (options.globals) runner.globals(options.globals); - if (options.growl) this._growl(runner, reporter); - return runner.run(fn); -}; - -}); // module: mocha.js - -require.register("reporters/base.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var tty = require('browser/tty') - , diff = require('browser/diff'); - -/** - * Save timer references to avoid Sinon interfering (see GH-237). - */ - -var Date = global.Date - , setTimeout = global.setTimeout - , setInterval = global.setInterval - , clearTimeout = global.clearTimeout - , clearInterval = global.clearInterval; - -/** - * Check if both stdio streams are associated with a tty. - */ - -var isatty = tty.isatty(1) && tty.isatty(2); - -/** - * Expose `Base`. - */ - -exports = module.exports = Base; - -/** - * Enable coloring by default. - */ - -exports.useColors = isatty; - -/** - * Default color map. - */ - -exports.colors = { - 'pass': 90 - , 'fail': 31 - , 'bright pass': 92 - , 'bright fail': 91 - , 'bright yellow': 93 - , 'pending': 36 - , 'suite': 0 - , 'error title': 0 - , 'error message': 31 - , 'error stack': 90 - , 'checkmark': 32 - , 'fast': 90 - , 'medium': 33 - , 'slow': 31 - , 'green': 32 - , 'light': 90 - , 'diff gutter': 90 - , 'diff added': 42 - , 'diff removed': 41 -}; - -/** - * Color `str` with the given `type`, - * allowing colors to be disabled, - * as well as user-defined color - * schemes. - * - * @param {String} type - * @param {String} str - * @return {String} - * @api private - */ - -var color = exports.color = function(type, str) { - if (!exports.useColors) return str; - return '\u001b[' + exports.colors[type] + 'm' + str + '\u001b[0m'; -}; - -/** - * Expose term window size, with some - * defaults for when stderr is not a tty. - */ - -exports.window = { - width: isatty - ? process.stdout.getWindowSize - ? process.stdout.getWindowSize(1)[0] - : tty.getWindowSize()[1] - : 75 -}; - -/** - * Expose some basic cursor interactions - * that are common among reporters. - */ - -exports.cursor = { - hide: function(){ - process.stdout.write('\u001b[?25l'); - }, - - show: function(){ - process.stdout.write('\u001b[?25h'); - }, - - deleteLine: function(){ - process.stdout.write('\u001b[2K'); - }, - - beginningOfLine: function(){ - process.stdout.write('\u001b[0G'); - }, - - CR: function(){ - exports.cursor.deleteLine(); - exports.cursor.beginningOfLine(); - } -}; - -/** - * A test is considered slow if it - * exceeds the following value in milliseconds. - */ - -exports.slow = 75; - -/** - * Outut the given `failures` as a list. - * - * @param {Array} failures - * @api public - */ - -exports.list = function(failures){ - console.error(); - failures.forEach(function(test, i){ - // format - var fmt = color('error title', ' %s) %s:\n') - + color('error message', ' %s') - + color('error stack', '\n%s\n'); - - // msg - var err = test.err - , message = err.message || '' - , stack = err.stack || message - , index = stack.indexOf(message) + message.length - , msg = stack.slice(0, index) - , actual = err.actual - , expected = err.expected; - - // actual / expected diff - if ('string' == typeof actual && 'string' == typeof expected) { - var len = Math.max(actual.length, expected.length); - - if (len < 20) msg = errorDiff(err, 'Chars'); - else msg = errorDiff(err, 'Words'); - - // linenos - var lines = msg.split('\n'); - if (lines.length > 4) { - var width = String(lines.length).length; - msg = lines.map(function(str, i){ - return pad(++i, width) + ' |' + ' ' + str; - }).join('\n'); - } - - // legend - msg = '\n' - + color('diff removed', 'actual') - + ' ' - + color('diff added', 'expected') - + '\n\n' - + msg - + '\n'; - - // indent - msg = msg.replace(/^/gm, ' '); - - fmt = color('error title', ' %s) %s:\n%s') - + color('error stack', '\n%s\n'); - } - - // indent stack trace without msg - stack = stack.slice(index ? index + 1 : index) - .replace(/^/gm, ' '); - - console.error(fmt, (i + 1), test.fullTitle(), msg, stack); - }); -}; - -/** - * Initialize a new `Base` reporter. - * - * All other reporters generally - * inherit from this reporter, providing - * stats such as test duration, number - * of tests passed / failed etc. - * - * @param {Runner} runner - * @api public - */ - -function Base(runner) { - var self = this - , stats = this.stats = { suites: 0, tests: 0, passes: 0, pending: 0, failures: 0 } - , failures = this.failures = []; - - if (!runner) return; - this.runner = runner; - - runner.on('start', function(){ - stats.start = new Date; - }); - - runner.on('suite', function(suite){ - stats.suites = stats.suites || 0; - suite.root || stats.suites++; - }); - - runner.on('test end', function(test){ - stats.tests = stats.tests || 0; - stats.tests++; - }); - - runner.on('pass', function(test){ - stats.passes = stats.passes || 0; - - var medium = exports.slow / 2; - test.speed = test.duration > exports.slow - ? 'slow' - : test.duration > medium - ? 'medium' - : 'fast'; - - stats.passes++; - }); - - runner.on('fail', function(test, err){ - stats.failures = stats.failures || 0; - stats.failures++; - test.err = err; - failures.push(test); - }); - - runner.on('end', function(){ - stats.end = new Date; - stats.duration = new Date - stats.start; - }); - - runner.on('pending', function(){ - stats.pending++; - }); -} - -/** - * Output common epilogue used by many of - * the bundled reporters. - * - * @api public - */ - -Base.prototype.epilogue = function(){ - var stats = this.stats - , fmt - , tests; - - console.log(); - - function pluralize(n) { - return 1 == n ? 'test' : 'tests'; - } - - // failure - if (stats.failures) { - fmt = color('bright fail', ' ✖') - + color('fail', ' %d of %d %s failed') - + color('light', ':') - - console.error(fmt, - stats.failures, - this.runner.total, - pluralize(this.runner.total)); - - Base.list(this.failures); - console.error(); - return; - } - - // pass - fmt = color('bright pass', ' ✔') - + color('green', ' %d %s complete') - + color('light', ' (%dms)'); - - console.log(fmt, - stats.tests || 0, - pluralize(stats.tests), - stats.duration); - - // pending - if (stats.pending) { - fmt = color('pending', ' •') - + color('pending', ' %d %s pending'); - - console.log(fmt, stats.pending, pluralize(stats.pending)); - } - - console.log(); -}; - -/** - * Pad the given `str` to `len`. - * - * @param {String} str - * @param {String} len - * @return {String} - * @api private - */ - -function pad(str, len) { - str = String(str); - return Array(len - str.length + 1).join(' ') + str; -} - -/** - * Return a character diff for `err`. - * - * @param {Error} err - * @return {String} - * @api private - */ - -function errorDiff(err, type) { - return diff['diff' + type](err.actual, err.expected).map(function(str){ - if (/^(\n+)$/.test(str.value)) str.value = Array(++RegExp.$1.length).join(''); - if (str.added) return colorLines('diff added', str.value); - if (str.removed) return colorLines('diff removed', str.value); - return str.value; - }).join(''); -} - -/** - * Color lines for `str`, using the color `name`. - * - * @param {String} name - * @param {String} str - * @return {String} - * @api private - */ - -function colorLines(name, str) { - return str.split('\n').map(function(str){ - return color(name, str); - }).join('\n'); -} - -}); // module: reporters/base.js - -require.register("reporters/doc.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Base = require('./base') - , utils = require('../utils'); - -/** - * Expose `Doc`. - */ - -exports = module.exports = Doc; - -/** - * Initialize a new `Doc` reporter. - * - * @param {Runner} runner - * @api public - */ - -function Doc(runner) { - Base.call(this, runner); - - var self = this - , stats = this.stats - , total = runner.total - , indents = 2; - - function indent() { - return Array(indents).join(' '); - } - - runner.on('suite', function(suite){ - if (suite.root) return; - ++indents; - console.log('%s
', indent()); - ++indents; - console.log('%s

%s

', indent(), suite.title); - console.log('%s
', indent()); - }); - - runner.on('suite end', function(suite){ - if (suite.root) return; - console.log('%s
', indent()); - --indents; - console.log('%s
', indent()); - --indents; - }); - - runner.on('pass', function(test){ - console.log('%s
%s
', indent(), test.title); - var code = utils.escape(utils.clean(test.fn.toString())); - console.log('%s
%s
', indent(), code); - }); -} - -}); // module: reporters/doc.js - -require.register("reporters/dot.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Base = require('./base') - , color = Base.color; - -/** - * Expose `Dot`. - */ - -exports = module.exports = Dot; - -/** - * Initialize a new `Dot` matrix test reporter. - * - * @param {Runner} runner - * @api public - */ - -function Dot(runner) { - Base.call(this, runner); - - var self = this - , stats = this.stats - , width = Base.window.width * .75 | 0 - , c = '․' - , n = 0; - - runner.on('start', function(){ - process.stdout.write('\n '); - }); - - runner.on('pending', function(test){ - process.stdout.write(color('pending', c)); - }); - - runner.on('pass', function(test){ - if (++n % width == 0) process.stdout.write('\n '); - if ('slow' == test.speed) { - process.stdout.write(color('bright yellow', c)); - } else { - process.stdout.write(color(test.speed, c)); - } - }); - - runner.on('fail', function(test, err){ - if (++n % width == 0) process.stdout.write('\n '); - process.stdout.write(color('fail', c)); - }); - - runner.on('end', function(){ - console.log(); - self.epilogue(); - }); -} - -/** - * Inherit from `Base.prototype`. - */ - -Dot.prototype = new Base; -Dot.prototype.constructor = Dot; - -}); // module: reporters/dot.js - -require.register("reporters/html-cov.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var JSONCov = require('./json-cov') - , fs = require('browser/fs'); - -/** - * Expose `HTMLCov`. - */ - -exports = module.exports = HTMLCov; - -/** - * Initialize a new `JsCoverage` reporter. - * - * @param {Runner} runner - * @api public - */ - -function HTMLCov(runner) { - var jade = require('jade') - , file = __dirname + '/templates/coverage.jade' - , str = fs.readFileSync(file, 'utf8') - , fn = jade.compile(str, { filename: file }) - , self = this; - - JSONCov.call(this, runner, false); - - runner.on('end', function(){ - process.stdout.write(fn({ - cov: self.cov - , coverageClass: coverageClass - })); - }); -} - -/** - * Return coverage class for `n`. - * - * @return {String} - * @api private - */ - -function coverageClass(n) { - if (n >= 75) return 'high'; - if (n >= 50) return 'medium'; - if (n >= 25) return 'low'; - return 'terrible'; -} -}); // module: reporters/html-cov.js - -require.register("reporters/html.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Base = require('./base') - , utils = require('../utils') - , Progress = require('../browser/progress') - , escape = utils.escape; - -/** - * Save timer references to avoid Sinon interfering (see GH-237). - */ - -var Date = global.Date - , setTimeout = global.setTimeout - , setInterval = global.setInterval - , clearTimeout = global.clearTimeout - , clearInterval = global.clearInterval; - -/** - * Expose `Doc`. - */ - -exports = module.exports = HTML; - -/** - * Stats template. - */ - -var statsTemplate = ''; - -/** - * Initialize a new `Doc` reporter. - * - * @param {Runner} runner - * @api public - */ - -function HTML(runner) { - Base.call(this, runner); - - var self = this - , stats = this.stats - , total = runner.total - , root = document.getElementById('mocha') - , stat = fragment(statsTemplate) - , items = stat.getElementsByTagName('li') - , passes = items[1].getElementsByTagName('em')[0] - , passesLink = items[1].getElementsByTagName('a')[0] - , failures = items[2].getElementsByTagName('em')[0] - , failuresLink = items[2].getElementsByTagName('a')[0] - , duration = items[3].getElementsByTagName('em')[0] - , canvas = stat.getElementsByTagName('canvas')[0] - , report = fragment('
    ') - , stack = [report] - , progress - , ctx - - if (canvas.getContext) { - ctx = canvas.getContext('2d'); - progress = new Progress; - } - - if (!root) return error('#mocha div missing, add it to your document'); - - // pass toggle - on(passesLink, 'click', function () { - var className = /pass/.test(report.className) ? '' : ' pass'; - report.className = report.className.replace(/fail|pass/g, '') + className; - }); - - // failure toggle - on(failuresLink, 'click', function () { - var className = /fail/.test(report.className) ? '' : ' fail'; - report.className = report.className.replace(/fail|pass/g, '') + className; - }); - - root.appendChild(stat); - root.appendChild(report); - - if (progress) progress.size(40); - - runner.on('suite', function(suite){ - if (suite.root) return; - - // suite - var url = location.protocol + '//' + location.host + location.pathname + '?grep=^' + utils.escapeRegexp(suite.fullTitle()); - var el = fragment('
  • %s

  • ', url, escape(suite.title)); - - // container - stack[0].appendChild(el); - stack.unshift(document.createElement('ul')); - el.appendChild(stack[0]); - }); - - runner.on('suite end', function(suite){ - if (suite.root) return; - stack.shift(); - }); - - runner.on('fail', function(test, err){ - if ('hook' == test.type || err.uncaught) runner.emit('test end', test); - }); - - runner.on('test end', function(test){ - window.scrollTo(0, document.body.scrollHeight); - - // TODO: add to stats - var percent = stats.tests / total * 100 | 0; - if (progress) progress.update(percent).draw(ctx); - - // update stats - var ms = new Date - stats.start; - text(passes, stats.passes); - text(failures, stats.failures); - text(duration, (ms / 1000).toFixed(2)); - - // test - if ('passed' == test.state) { - var el = fragment('
  • %e%ems

  • ', test.speed, test.title, test.duration); - } else if (test.pending) { - var el = fragment('
  • %e

  • ', test.title); - } else { - var el = fragment('
  • %e

  • ', test.title); - var str = test.err.stack || test.err.toString(); - - // FF / Opera do not add the message - if (!~str.indexOf(test.err.message)) { - str = test.err.message + '\n' + str; - } - - // <=IE7 stringifies to [Object Error]. Since it can be overloaded, we - // check for the result of the stringifying. - if ('[object Error]' == str) str = test.err.message; - - // Safari doesn't give you a stack. Let's at least provide a source line. - if (!test.err.stack && test.err.sourceURL && test.err.line !== undefined) { - str += "\n(" + test.err.sourceURL + ":" + test.err.line + ")"; - } - - el.appendChild(fragment('
    %e
    ', str)); - } - - // toggle code - // TODO: defer - if (!test.pending) { - var h2 = el.getElementsByTagName('h2')[0]; - - on(h2, 'click', function(){ - pre.style.display = 'none' == pre.style.display - ? 'inline-block' - : 'none'; - }); - - var pre = fragment('
    %e
    ', utils.clean(test.fn.toString())); - el.appendChild(pre); - pre.style.display = 'none'; - } - - stack[0].appendChild(el); - }); -} - -/** - * Display error `msg`. - */ - -function error(msg) { - document.body.appendChild(fragment('
    %s
    ', msg)); -} - -/** - * Return a DOM fragment from `html`. - */ - -function fragment(html) { - var args = arguments - , div = document.createElement('div') - , i = 1; - - div.innerHTML = html.replace(/%([se])/g, function(_, type){ - switch (type) { - case 's': return String(args[i++]); - case 'e': return escape(args[i++]); - } - }); - - return div.firstChild; -} - -/** - * Set `el` text to `str`. - */ - -function text(el, str) { - if (el.textContent) { - el.textContent = str; - } else { - el.innerText = str; - } -} - -/** - * Listen on `event` with callback `fn`. - */ - -function on(el, event, fn) { - if (el.addEventListener) { - el.addEventListener(event, fn, false); - } else { - el.attachEvent('on' + event, fn); - } -} - -}); // module: reporters/html.js - -require.register("reporters/index.js", function(module, exports, require){ - -exports.Base = require('./base'); -exports.Dot = require('./dot'); -exports.Doc = require('./doc'); -exports.TAP = require('./tap'); -exports.JSON = require('./json'); -exports.HTML = require('./html'); -exports.List = require('./list'); -exports.Min = require('./min'); -exports.Spec = require('./spec'); -exports.Nyan = require('./nyan'); -exports.XUnit = require('./xunit'); -exports.Progress = require('./progress'); -exports.Landing = require('./landing'); -exports.JSONCov = require('./json-cov'); -exports.HTMLCov = require('./html-cov'); -exports.JSONStream = require('./json-stream'); -exports.Teamcity = require('./teamcity'); - -}); // module: reporters/index.js - -require.register("reporters/json-cov.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Base = require('./base'); - -/** - * Expose `JSONCov`. - */ - -exports = module.exports = JSONCov; - -/** - * Initialize a new `JsCoverage` reporter. - * - * @param {Runner} runner - * @param {Boolean} output - * @api public - */ - -function JSONCov(runner, output) { - var self = this - , output = 1 == arguments.length ? true : output; - - Base.call(this, runner); - - var tests = [] - , failures = [] - , passes = []; - - runner.on('test end', function(test){ - tests.push(test); - }); - - runner.on('pass', function(test){ - passes.push(test); - }); - - runner.on('fail', function(test){ - failures.push(test); - }); - - runner.on('end', function(){ - var cov = global._$jscoverage || {}; - var result = self.cov = map(cov); - result.stats = self.stats; - result.tests = tests.map(clean); - result.failures = failures.map(clean); - result.passes = passes.map(clean); - if (!output) return; - process.stdout.write(JSON.stringify(result, null, 2 )); - }); -} - -/** - * Map jscoverage data to a JSON structure - * suitable for reporting. - * - * @param {Object} cov - * @return {Object} - * @api private - */ - -function map(cov) { - var ret = { - instrumentation: 'node-jscoverage' - , sloc: 0 - , hits: 0 - , misses: 0 - , coverage: 0 - , files: [] - }; - - for (var filename in cov) { - var data = coverage(filename, cov[filename]); - ret.files.push(data); - ret.hits += data.hits; - ret.misses += data.misses; - ret.sloc += data.sloc; - } - - if (ret.sloc > 0) { - ret.coverage = (ret.hits / ret.sloc) * 100; - } - - return ret; -}; - -/** - * Map jscoverage data for a single source file - * to a JSON structure suitable for reporting. - * - * @param {String} filename name of the source file - * @param {Object} data jscoverage coverage data - * @return {Object} - * @api private - */ - -function coverage(filename, data) { - var ret = { - filename: filename, - coverage: 0, - hits: 0, - misses: 0, - sloc: 0, - source: {} - }; - - data.source.forEach(function(line, num){ - num++; - - if (data[num] === 0) { - ret.misses++; - ret.sloc++; - } else if (data[num] !== undefined) { - ret.hits++; - ret.sloc++; - } - - ret.source[num] = { - source: line - , coverage: data[num] === undefined - ? '' - : data[num] - }; - }); - - ret.coverage = ret.hits / ret.sloc * 100; - - return ret; -} - -/** - * Return a plain-object representation of `test` - * free of cyclic properties etc. - * - * @param {Object} test - * @return {Object} - * @api private - */ - -function clean(test) { - return { - title: test.title - , fullTitle: test.fullTitle() - , duration: test.duration - } -} - -}); // module: reporters/json-cov.js - -require.register("reporters/json-stream.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Base = require('./base') - , color = Base.color; - -/** - * Expose `List`. - */ - -exports = module.exports = List; - -/** - * Initialize a new `List` test reporter. - * - * @param {Runner} runner - * @api public - */ - -function List(runner) { - Base.call(this, runner); - - var self = this - , stats = this.stats - , total = runner.total; - - runner.on('start', function(){ - console.log(JSON.stringify(['start', { total: total }])); - }); - - runner.on('pass', function(test){ - console.log(JSON.stringify(['pass', clean(test)])); - }); - - runner.on('fail', function(test, err){ - console.log(JSON.stringify(['fail', clean(test)])); - }); - - runner.on('end', function(){ - process.stdout.write(JSON.stringify(['end', self.stats])); - }); -} - -/** - * Return a plain-object representation of `test` - * free of cyclic properties etc. - * - * @param {Object} test - * @return {Object} - * @api private - */ - -function clean(test) { - return { - title: test.title - , fullTitle: test.fullTitle() - , duration: test.duration - } -} -}); // module: reporters/json-stream.js - -require.register("reporters/json.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Base = require('./base') - , cursor = Base.cursor - , color = Base.color; - -/** - * Expose `JSON`. - */ - -exports = module.exports = JSONReporter; - -/** - * Initialize a new `JSON` reporter. - * - * @param {Runner} runner - * @api public - */ - -function JSONReporter(runner) { - var self = this; - Base.call(this, runner); - - var tests = [] - , failures = [] - , passes = []; - - runner.on('test end', function(test){ - tests.push(test); - }); - - runner.on('pass', function(test){ - passes.push(test); - }); - - runner.on('fail', function(test){ - failures.push(test); - }); - - runner.on('end', function(){ - var obj = { - stats: self.stats - , tests: tests.map(clean) - , failures: failures.map(clean) - , passes: passes.map(clean) - }; - - process.stdout.write(JSON.stringify(obj, null, 2)); - }); -} - -/** - * Return a plain-object representation of `test` - * free of cyclic properties etc. - * - * @param {Object} test - * @return {Object} - * @api private - */ - -function clean(test) { - return { - title: test.title - , fullTitle: test.fullTitle() - , duration: test.duration - } -} -}); // module: reporters/json.js - -require.register("reporters/landing.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Base = require('./base') - , cursor = Base.cursor - , color = Base.color; - -/** - * Expose `Landing`. - */ - -exports = module.exports = Landing; - -/** - * Airplane color. - */ - -Base.colors.plane = 0; - -/** - * Airplane crash color. - */ - -Base.colors['plane crash'] = 31; - -/** - * Runway color. - */ - -Base.colors.runway = 90; - -/** - * Initialize a new `Landing` reporter. - * - * @param {Runner} runner - * @api public - */ - -function Landing(runner) { - Base.call(this, runner); - - var self = this - , stats = this.stats - , width = Base.window.width * .75 | 0 - , total = runner.total - , stream = process.stdout - , plane = color('plane', '✈') - , crashed = -1 - , n = 0; - - function runway() { - var buf = Array(width).join('-'); - return ' ' + color('runway', buf); - } - - runner.on('start', function(){ - stream.write('\n '); - cursor.hide(); - }); - - runner.on('test end', function(test){ - // check if the plane crashed - var col = -1 == crashed - ? width * ++n / total | 0 - : crashed; - - // show the crash - if ('failed' == test.state) { - plane = color('plane crash', '✈'); - crashed = col; - } - - // render landing strip - stream.write('\u001b[4F\n\n'); - stream.write(runway()); - stream.write('\n '); - stream.write(color('runway', Array(col).join('⋅'))); - stream.write(plane) - stream.write(color('runway', Array(width - col).join('⋅') + '\n')); - stream.write(runway()); - stream.write('\u001b[0m'); - }); - - runner.on('end', function(){ - cursor.show(); - console.log(); - self.epilogue(); - }); -} - -/** - * Inherit from `Base.prototype`. - */ - -Landing.prototype = new Base; -Landing.prototype.constructor = Landing; - -}); // module: reporters/landing.js - -require.register("reporters/list.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Base = require('./base') - , cursor = Base.cursor - , color = Base.color; - -/** - * Expose `List`. - */ - -exports = module.exports = List; - -/** - * Initialize a new `List` test reporter. - * - * @param {Runner} runner - * @api public - */ - -function List(runner) { - Base.call(this, runner); - - var self = this - , stats = this.stats - , n = 0; - - runner.on('start', function(){ - console.log(); - }); - - runner.on('test', function(test){ - process.stdout.write(color('pass', ' ' + test.fullTitle() + ': ')); - }); - - runner.on('pending', function(test){ - var fmt = color('checkmark', ' -') - + color('pending', ' %s'); - console.log(fmt, test.fullTitle()); - }); - - runner.on('pass', function(test){ - var fmt = color('checkmark', ' ✓') - + color('pass', ' %s: ') - + color(test.speed, '%dms'); - cursor.CR(); - console.log(fmt, test.fullTitle(), test.duration); - }); - - runner.on('fail', function(test, err){ - cursor.CR(); - console.log(color('fail', ' %d) %s'), ++n, test.fullTitle()); - }); - - runner.on('end', self.epilogue.bind(self)); -} - -/** - * Inherit from `Base.prototype`. - */ - -List.prototype = new Base; -List.prototype.constructor = List; - - -}); // module: reporters/list.js - -require.register("reporters/markdown.js", function(module, exports, require){ -/** - * Module dependencies. - */ - -var Base = require('./base') - , utils = require('../utils'); - -/** - * Expose `Markdown`. - */ - -exports = module.exports = Markdown; - -/** - * Initialize a new `Markdown` reporter. - * - * @param {Runner} runner - * @api public - */ - -function Markdown(runner) { - Base.call(this, runner); - - var self = this - , stats = this.stats - , total = runner.total - , level = 0 - , buf = ''; - - function title(str) { - return Array(level).join('#') + ' ' + str; - } - - function indent() { - return Array(level).join(' '); - } - - function mapTOC(suite, obj) { - var ret = obj; - obj = obj[suite.title] = obj[suite.title] || { suite: suite }; - suite.suites.forEach(function(suite){ - mapTOC(suite, obj); - }); - return ret; - } - - function stringifyTOC(obj, level) { - ++level; - var buf = ''; - var link; - for (var key in obj) { - if ('suite' == key) continue; - if (key) link = ' - [' + key + '](#' + utils.slug(obj[key].suite.fullTitle()) + ')\n'; - if (key) buf += Array(level).join(' ') + link; - buf += stringifyTOC(obj[key], level); - } - --level; - return buf; - } - - function generateTOC(suite) { - var obj = mapTOC(suite, {}); - return stringifyTOC(obj, 0); - } - - generateTOC(runner.suite); - - runner.on('suite', function(suite){ - ++level; - var slug = utils.slug(suite.fullTitle()); - buf += '' + '\n'; - buf += title(suite.title) + '\n'; - }); - - runner.on('suite end', function(suite){ - --level; - }); - - runner.on('pass', function(test){ - var code = utils.clean(test.fn.toString()); - buf += test.title + '.\n'; - buf += '\n```js\n'; - buf += code + '\n'; - buf += '```\n\n'; - }); - - runner.on('end', function(){ - process.stdout.write('# TOC\n'); - process.stdout.write(generateTOC(runner.suite)); - process.stdout.write(buf); - }); -} -}); // module: reporters/markdown.js - -require.register("reporters/min.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Base = require('./base'); - -/** - * Expose `Min`. - */ - -exports = module.exports = Min; - -/** - * Initialize a new `Min` minimal test reporter (best used with --watch). - * - * @param {Runner} runner - * @api public - */ - -function Min(runner) { - Base.call(this, runner); - - runner.on('start', function(){ - // clear screen - process.stdout.write('\u001b[2J'); - // set cursor position - process.stdout.write('\u001b[1;3H'); - }); - - runner.on('end', this.epilogue.bind(this)); -} - -/** - * Inherit from `Base.prototype`. - */ - -Min.prototype = new Base; -Min.prototype.constructor = Min; - -}); // module: reporters/min.js - -require.register("reporters/nyan.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Base = require('./base') - , color = Base.color; - -/** - * Expose `Dot`. - */ - -exports = module.exports = NyanCat; - -/** - * Initialize a new `Dot` matrix test reporter. - * - * @param {Runner} runner - * @api public - */ - -function NyanCat(runner) { - Base.call(this, runner); - - var self = this - , stats = this.stats - , width = Base.window.width * .75 | 0 - , rainbowColors = this.rainbowColors = self.generateColors() - , colorIndex = this.colorIndex = 0 - , numerOfLines = this.numberOfLines = 4 - , trajectories = this.trajectories = [[], [], [], []] - , nyanCatWidth = this.nyanCatWidth = 11 - , trajectoryWidthMax = this.trajectoryWidthMax = (width - nyanCatWidth) - , scoreboardWidth = this.scoreboardWidth = 5 - , tick = this.tick = 0 - , n = 0; - - runner.on('start', function(){ - Base.cursor.hide(); - self.draw('start'); - }); - - runner.on('pending', function(test){ - self.draw('pending'); - }); - - runner.on('pass', function(test){ - self.draw('pass'); - }); - - runner.on('fail', function(test, err){ - self.draw('fail'); - }); - - runner.on('end', function(){ - Base.cursor.show(); - for (var i = 0; i < self.numberOfLines; i++) write('\n'); - self.epilogue(); - }); -} - -/** - * Draw the nyan cat with runner `status`. - * - * @param {String} status - * @api private - */ - -NyanCat.prototype.draw = function(status){ - this.appendRainbow(); - this.drawScoreboard(); - this.drawRainbow(); - this.drawNyanCat(status); - this.tick = !this.tick; -}; - -/** - * Draw the "scoreboard" showing the number - * of passes, failures and pending tests. - * - * @api private - */ - -NyanCat.prototype.drawScoreboard = function(){ - var stats = this.stats; - var colors = Base.colors; - - function draw(color, n) { - write(' '); - write('\u001b[' + color + 'm' + n + '\u001b[0m'); - write('\n'); - } - - draw(colors.green, stats.passes); - draw(colors.fail, stats.failures); - draw(colors.pending, stats.pending); - write('\n'); - - this.cursorUp(this.numberOfLines); -}; - -/** - * Append the rainbow. - * - * @api private - */ - -NyanCat.prototype.appendRainbow = function(){ - var segment = this.tick ? '_' : '-'; - var rainbowified = this.rainbowify(segment); - - for (var index = 0; index < this.numberOfLines; index++) { - var trajectory = this.trajectories[index]; - if (trajectory.length >= this.trajectoryWidthMax) trajectory.shift(); - trajectory.push(rainbowified); - } -}; - -/** - * Draw the rainbow. - * - * @api private - */ - -NyanCat.prototype.drawRainbow = function(){ - var self = this; - - this.trajectories.forEach(function(line, index) { - write('\u001b[' + self.scoreboardWidth + 'C'); - write(line.join('')); - write('\n'); - }); - - this.cursorUp(this.numberOfLines); -}; - -/** - * Draw the nyan cat with `status`. - * - * @param {String} status - * @api private - */ - -NyanCat.prototype.drawNyanCat = function(status) { - var self = this; - var startWidth = this.scoreboardWidth + this.trajectories[0].length; - - [0, 1, 2, 3].forEach(function(index) { - write('\u001b[' + startWidth + 'C'); - - switch (index) { - case 0: - write('_,------,'); - write('\n'); - break; - case 1: - var padding = self.tick ? ' ' : ' '; - write('_|' + padding + '/\\_/\\ '); - write('\n'); - break; - case 2: - var padding = self.tick ? '_' : '__'; - var tail = self.tick ? '~' : '^'; - var face; - switch (status) { - case 'pass': - face = '( ^ .^)'; - break; - case 'fail': - face = '( o .o)'; - break; - default: - face = '( - .-)'; - } - write(tail + '|' + padding + face + ' '); - write('\n'); - break; - case 3: - var padding = self.tick ? ' ' : ' '; - write(padding + '"" "" '); - write('\n'); - break; - } - }); - - this.cursorUp(this.numberOfLines); -}; - -/** - * Move cursor up `n`. - * - * @param {Number} n - * @api private - */ - -NyanCat.prototype.cursorUp = function(n) { - write('\u001b[' + n + 'A'); -}; - -/** - * Move cursor down `n`. - * - * @param {Number} n - * @api private - */ - -NyanCat.prototype.cursorDown = function(n) { - write('\u001b[' + n + 'B'); -}; - -/** - * Generate rainbow colors. - * - * @return {Array} - * @api private - */ - -NyanCat.prototype.generateColors = function(){ - var colors = []; - - for (var i = 0; i < (6 * 7); i++) { - var pi3 = Math.floor(Math.PI / 3); - var n = (i * (1.0 / 6)); - var r = Math.floor(3 * Math.sin(n) + 3); - var g = Math.floor(3 * Math.sin(n + 2 * pi3) + 3); - var b = Math.floor(3 * Math.sin(n + 4 * pi3) + 3); - colors.push(36 * r + 6 * g + b + 16); - } - - return colors; -}; - -/** - * Apply rainbow to the given `str`. - * - * @param {String} str - * @return {String} - * @api private - */ - -NyanCat.prototype.rainbowify = function(str){ - var color = this.rainbowColors[this.colorIndex % this.rainbowColors.length]; - this.colorIndex += 1; - return '\u001b[38;5;' + color + 'm' + str + '\u001b[0m'; -}; - -/** - * Stdout helper. - */ - -function write(string) { - process.stdout.write(string); -} - -/** - * Inherit from `Base.prototype`. - */ - -NyanCat.prototype = new Base; -NyanCat.prototype.constructor = NyanCat; - - -}); // module: reporters/nyan.js - -require.register("reporters/progress.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Base = require('./base') - , cursor = Base.cursor - , color = Base.color; - -/** - * Expose `Progress`. - */ - -exports = module.exports = Progress; - -/** - * General progress bar color. - */ - -Base.colors.progress = 90; - -/** - * Initialize a new `Progress` bar test reporter. - * - * @param {Runner} runner - * @param {Object} options - * @api public - */ - -function Progress(runner, options) { - Base.call(this, runner); - - var self = this - , options = options || {} - , stats = this.stats - , width = Base.window.width * .50 | 0 - , total = runner.total - , complete = 0 - , max = Math.max; - - // default chars - options.open = options.open || '['; - options.complete = options.complete || '▬'; - options.incomplete = options.incomplete || '⋅'; - options.close = options.close || ']'; - options.verbose = false; - - // tests started - runner.on('start', function(){ - console.log(); - cursor.hide(); - }); - - // tests complete - runner.on('test end', function(){ - complete++; - var incomplete = total - complete - , percent = complete / total - , n = width * percent | 0 - , i = width - n; - - cursor.CR(); - process.stdout.write('\u001b[J'); - process.stdout.write(color('progress', ' ' + options.open)); - process.stdout.write(Array(n).join(options.complete)); - process.stdout.write(Array(i).join(options.incomplete)); - process.stdout.write(color('progress', options.close)); - if (options.verbose) { - process.stdout.write(color('progress', ' ' + complete + ' of ' + total)); - } - }); - - // tests are complete, output some stats - // and the failures if any - runner.on('end', function(){ - cursor.show(); - console.log(); - self.epilogue(); - }); -} - -/** - * Inherit from `Base.prototype`. - */ - -Progress.prototype = new Base; -Progress.prototype.constructor = Progress; - - -}); // module: reporters/progress.js - -require.register("reporters/spec.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Base = require('./base') - , cursor = Base.cursor - , color = Base.color; - -/** - * Expose `Spec`. - */ - -exports = module.exports = Spec; - -/** - * Initialize a new `Spec` test reporter. - * - * @param {Runner} runner - * @api public - */ - -function Spec(runner) { - Base.call(this, runner); - - var self = this - , stats = this.stats - , indents = 0 - , n = 0; - - function indent() { - return Array(indents).join(' ') - } - - runner.on('start', function(){ - console.log(); - }); - - runner.on('suite', function(suite){ - ++indents; - console.log(color('suite', '%s%s'), indent(), suite.title); - }); - - runner.on('suite end', function(suite){ - --indents; - if (1 == indents) console.log(); - }); - - runner.on('test', function(test){ - process.stdout.write(indent() + color('pass', ' ◦ ' + test.title + ': ')); - }); - - runner.on('pending', function(test){ - var fmt = indent() + color('pending', ' - %s'); - console.log(fmt, test.title); - }); - - runner.on('pass', function(test){ - if ('fast' == test.speed) { - var fmt = indent() - + color('checkmark', ' ✓') - + color('pass', ' %s '); - cursor.CR(); - console.log(fmt, test.title); - } else { - var fmt = indent() - + color('checkmark', ' ✓') - + color('pass', ' %s ') - + color(test.speed, '(%dms)'); - cursor.CR(); - console.log(fmt, test.title, test.duration); - } - }); - - runner.on('fail', function(test, err){ - cursor.CR(); - console.log(indent() + color('fail', ' %d) %s'), ++n, test.title); - }); - - runner.on('end', self.epilogue.bind(self)); -} - -/** - * Inherit from `Base.prototype`. - */ - -Spec.prototype = new Base; -Spec.prototype.constructor = Spec; - - -}); // module: reporters/spec.js - -require.register("reporters/tap.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Base = require('./base') - , cursor = Base.cursor - , color = Base.color; - -/** - * Expose `TAP`. - */ - -exports = module.exports = TAP; - -/** - * Initialize a new `TAP` reporter. - * - * @param {Runner} runner - * @api public - */ - -function TAP(runner) { - Base.call(this, runner); - - var self = this - , stats = this.stats - , total = runner.total - , n = 1; - - runner.on('start', function(){ - console.log('%d..%d', 1, total); - }); - - runner.on('test end', function(){ - ++n; - }); - - runner.on('pending', function(test){ - console.log('ok %d %s # SKIP -', n, title(test)); - }); - - runner.on('pass', function(test){ - console.log('ok %d %s', n, title(test)); - }); - - runner.on('fail', function(test, err){ - console.log('not ok %d %s', n, title(test)); - console.log(err.stack.replace(/^/gm, ' ')); - }); -} - -/** - * Return a TAP-safe title of `test` - * - * @param {Object} test - * @return {String} - * @api private - */ - -function title(test) { - return test.fullTitle().replace(/#/g, ''); -} - -}); // module: reporters/tap.js - -require.register("reporters/teamcity.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Base = require('./base'); - -/** - * Expose `Teamcity`. - */ - -exports = module.exports = Teamcity; - -/** - * Initialize a new `Teamcity` reporter. - * - * @param {Runner} runner - * @api public - */ - -function Teamcity(runner) { - Base.call(this, runner); - var stats = this.stats; - - runner.on('start', function() { - console.log("##teamcity[testSuiteStarted name='mocha.suite']"); - }); - - runner.on('test', function(test) { - console.log("##teamcity[testStarted name='" + escape(test.fullTitle()) + "']"); - }); - - runner.on('fail', function(test, err) { - console.log("##teamcity[testFailed name='" + escape(test.fullTitle()) + "' message='" + escape(err.message) + "']"); - }); - - runner.on('pending', function(test) { - console.log("##teamcity[testIgnored name='" + escape(test.fullTitle()) + "' message='pending']"); - }); - - runner.on('test end', function(test) { - console.log("##teamcity[testFinished name='" + escape(test.fullTitle()) + "' duration='" + test.duration + "']"); - }); - - runner.on('end', function() { - console.log("##teamcity[testSuiteFinished name='mocha.suite' duration='" + stats.duration + "']"); - }); -} - -/** - * Escape the given `str`. - */ - -function escape(str) { - return str - .replace(/\|/g, "||") - .replace(/\n/g, "|n") - .replace(/\r/g, "|r") - .replace(/\[/g, "|[") - .replace(/\]/g, "|]") - .replace(/\u0085/g, "|x") - .replace(/\u2028/g, "|l") - .replace(/\u2029/g, "|p") - .replace(/'/g, "|'"); -} - -}); // module: reporters/teamcity.js - -require.register("reporters/xunit.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Base = require('./base') - , utils = require('../utils') - , escape = utils.escape; - -/** - * Save timer references to avoid Sinon interfering (see GH-237). - */ - -var Date = global.Date - , setTimeout = global.setTimeout - , setInterval = global.setInterval - , clearTimeout = global.clearTimeout - , clearInterval = global.clearInterval; - -/** - * Expose `XUnit`. - */ - -exports = module.exports = XUnit; - -/** - * Initialize a new `XUnit` reporter. - * - * @param {Runner} runner - * @api public - */ - -function XUnit(runner) { - Base.call(this, runner); - var stats = this.stats - , tests = [] - , self = this; - - runner.on('pass', function(test){ - tests.push(test); - }); - - runner.on('fail', function(test){ - tests.push(test); - }); - - runner.on('end', function(){ - console.log(tag('testsuite', { - name: 'Mocha Tests' - , tests: stats.tests - , failures: stats.failures - , errors: stats.failures - , skip: stats.tests - stats.failures - stats.passes - , timestamp: (new Date).toUTCString() - , time: stats.duration / 1000 - }, false)); - - tests.forEach(test); - console.log(''); - }); -} - -/** - * Inherit from `Base.prototype`. - */ - -XUnit.prototype = new Base; -XUnit.prototype.constructor = XUnit; - - -/** - * Output tag for the given `test.` - */ - -function test(test) { - var attrs = { - classname: test.parent.fullTitle() - , name: test.title - , time: test.duration / 1000 - }; - - if ('failed' == test.state) { - var err = test.err; - attrs.message = escape(err.message); - console.log(tag('testcase', attrs, false, tag('failure', attrs, false, cdata(err.stack)))); - } else if (test.pending) { - console.log(tag('testcase', attrs, false, tag('skipped', {}, true))); - } else { - console.log(tag('testcase', attrs, true) ); - } -} - -/** - * HTML tag helper. - */ - -function tag(name, attrs, close, content) { - var end = close ? '/>' : '>' - , pairs = [] - , tag; - - for (var key in attrs) { - pairs.push(key + '="' + escape(attrs[key]) + '"'); - } - - tag = '<' + name + (pairs.length ? ' ' + pairs.join(' ') : '') + end; - if (content) tag += content + ''; -} - -}); // module: reporters/xunit.js - -require.register("runnable.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var EventEmitter = require('browser/events').EventEmitter - , debug = require('browser/debug')('mocha:runnable'); - -/** - * Save timer references to avoid Sinon interfering (see GH-237). - */ - -var Date = global.Date - , setTimeout = global.setTimeout - , setInterval = global.setInterval - , clearTimeout = global.clearTimeout - , clearInterval = global.clearInterval; - -/** - * Expose `Runnable`. - */ - -module.exports = Runnable; - -/** - * Initialize a new `Runnable` with the given `title` and callback `fn`. - * - * @param {String} title - * @param {Function} fn - * @api private - */ - -function Runnable(title, fn) { - this.title = title; - this.fn = fn; - this.async = fn && fn.length; - this.sync = ! this.async; - this._timeout = 2000; - this.timedOut = false; -} - -/** - * Inherit from `EventEmitter.prototype`. - */ - -Runnable.prototype = new EventEmitter; -Runnable.prototype.constructor = Runnable; - - -/** - * Set & get timeout `ms`. - * - * @param {Number} ms - * @return {Runnable|Number} ms or self - * @api private - */ - -Runnable.prototype.timeout = function(ms){ - if (0 == arguments.length) return this._timeout; - debug('timeout %d', ms); - this._timeout = ms; - if (this.timer) this.resetTimeout(); - return this; -}; - -/** - * Return the full title generated by recursively - * concatenating the parent's full title. - * - * @return {String} - * @api public - */ - -Runnable.prototype.fullTitle = function(){ - return this.parent.fullTitle() + ' ' + this.title; -}; - -/** - * Clear the timeout. - * - * @api private - */ - -Runnable.prototype.clearTimeout = function(){ - clearTimeout(this.timer); -}; - -/** - * Inspect the runnable void of private properties. - * - * @return {String} - * @api private - */ - -Runnable.prototype.inspect = function(){ - return JSON.stringify(this, function(key, val){ - if ('_' == key[0]) return; - if ('parent' == key) return '#'; - if ('ctx' == key) return '#'; - return val; - }, 2); -}; - -/** - * Reset the timeout. - * - * @api private - */ - -Runnable.prototype.resetTimeout = function(){ - var self = this - , ms = this.timeout(); - - this.clearTimeout(); - if (ms) { - this.timer = setTimeout(function(){ - self.callback(new Error('timeout of ' + ms + 'ms exceeded')); - self.timedOut = true; - }, ms); - } -}; - -/** - * Run the test and invoke `fn(err)`. - * - * @param {Function} fn - * @api private - */ - -Runnable.prototype.run = function(fn){ - var self = this - , ms = this.timeout() - , start = new Date - , ctx = this.ctx - , finished - , emitted; - - if (ctx) ctx.runnable(this); - - // timeout - if (this.async) { - if (ms) { - this.timer = setTimeout(function(){ - done(new Error('timeout of ' + ms + 'ms exceeded')); - self.timedOut = true; - }, ms); - } - } - - // called multiple times - function multiple(err) { - if (emitted) return; - emitted = true; - self.emit('error', err || new Error('done() called multiple times')); - } - - // finished - function done(err) { - if (self.timedOut) return; - if (finished) return multiple(err); - self.clearTimeout(); - self.duration = new Date - start; - finished = true; - fn(err); - } - - // for .resetTimeout() - this.callback = done; - - // async - if (this.async) { - try { - this.fn.call(ctx, function(err){ - if (err instanceof Error) return done(err); - if (null != err) return done(new Error('done() invoked with non-Error: ' + err)); - done(); - }); - } catch (err) { - done(err); - } - return; - } - - // sync - try { - if (!this.pending) this.fn.call(ctx); - this.duration = new Date - start; - fn(); - } catch (err) { - fn(err); - } -}; - -}); // module: runnable.js - -require.register("runner.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var EventEmitter = require('browser/events').EventEmitter - , debug = require('browser/debug')('mocha:runner') - , Test = require('./test') - , utils = require('./utils') - , filter = utils.filter - , keys = utils.keys - , noop = function(){}; - -/** - * Expose `Runner`. - */ - -module.exports = Runner; - -/** - * Initialize a `Runner` for the given `suite`. - * - * Events: - * - * - `start` execution started - * - `end` execution complete - * - `suite` (suite) test suite execution started - * - `suite end` (suite) all tests (and sub-suites) have finished - * - `test` (test) test execution started - * - `test end` (test) test completed - * - `hook` (hook) hook execution started - * - `hook end` (hook) hook complete - * - `pass` (test) test passed - * - `fail` (test, err) test failed - * - * @api public - */ - -function Runner(suite) { - var self = this; - this._globals = []; - this.suite = suite; - this.total = suite.total(); - this.failures = 0; - this.on('test end', function(test){ self.checkGlobals(test); }); - this.on('hook end', function(hook){ self.checkGlobals(hook); }); - this.grep(/.*/); - this.globals(utils.keys(global).concat(['errno'])); -} - -/** - * Inherit from `EventEmitter.prototype`. - */ - -Runner.prototype = new EventEmitter; -Runner.prototype.constructor = Runner; - - -/** - * Run tests with full titles matching `re`. Updates runner.total - * with number of tests matched. - * - * @param {RegExp} re - * @param {Boolean} invert - * @return {Runner} for chaining - * @api public - */ - -Runner.prototype.grep = function(re, invert){ - debug('grep %s', re); - this._grep = re; - this._invert = invert; - this.total = this.grepTotal(this.suite); - return this; -}; - -/** - * Returns the number of tests matching the grep search for the - * given suite. - * - * @param {Suite} suite - * @return {Number} - * @api public - */ - -Runner.prototype.grepTotal = function(suite) { - var self = this; - var total = 0; - - suite.eachTest(function(test){ - var match = self._grep.test(test.fullTitle()); - if (self._invert) match = !match; - if (match) total++; - }); - - return total; -}; - -/** - * Allow the given `arr` of globals. - * - * @param {Array} arr - * @return {Runner} for chaining - * @api public - */ - -Runner.prototype.globals = function(arr){ - if (0 == arguments.length) return this._globals; - debug('globals %j', arr); - utils.forEach(arr, function(arr){ - this._globals.push(arr); - }, this); - return this; -}; - -/** - * Check for global variable leaks. - * - * @api private - */ - -Runner.prototype.checkGlobals = function(test){ - if (this.ignoreLeaks) return; - var leaks = filterLeaks(this._globals); - - this._globals = this._globals.concat(leaks); - - if (leaks.length > 1) { - this.fail(test, new Error('global leaks detected: ' + leaks.join(', ') + '')); - } else if (leaks.length) { - this.fail(test, new Error('global leak detected: ' + leaks[0])); - } -}; - -/** - * Fail the given `test`. - * - * @param {Test} test - * @param {Error} err - * @api private - */ - -Runner.prototype.fail = function(test, err){ - ++this.failures; - test.state = 'failed'; - if ('string' == typeof err) { - err = new Error('the string "' + err + '" was thrown, throw an Error :)'); - } - this.emit('fail', test, err); -}; - -/** - * Fail the given `hook` with `err`. - * - * Hook failures (currently) hard-end due - * to that fact that a failing hook will - * surely cause subsequent tests to fail, - * causing jumbled reporting. - * - * @param {Hook} hook - * @param {Error} err - * @api private - */ - -Runner.prototype.failHook = function(hook, err){ - this.fail(hook, err); - this.emit('end'); -}; - -/** - * Run hook `name` callbacks and then invoke `fn()`. - * - * @param {String} name - * @param {Function} function - * @api private - */ - -Runner.prototype.hook = function(name, fn){ - var suite = this.suite - , hooks = suite['_' + name] - , ms = suite._timeout - , self = this - , timer; - - function next(i) { - var hook = hooks[i]; - if (!hook) return fn(); - self.currentRunnable = hook; - - self.emit('hook', hook); - - hook.on('error', function(err){ - self.failHook(hook, err); - }); - - hook.run(function(err){ - hook.removeAllListeners('error'); - var testError = hook.error(); - if (testError) self.fail(self.test, testError); - if (err) return self.failHook(hook, err); - self.emit('hook end', hook); - next(++i); - }); - } - - process.nextTick(function(){ - next(0); - }); -}; - -/** - * Run hook `name` for the given array of `suites` - * in order, and callback `fn(err)`. - * - * @param {String} name - * @param {Array} suites - * @param {Function} fn - * @api private - */ - -Runner.prototype.hooks = function(name, suites, fn){ - var self = this - , orig = this.suite; - - function next(suite) { - self.suite = suite; - - if (!suite) { - self.suite = orig; - return fn(); - } - - self.hook(name, function(err){ - if (err) { - self.suite = orig; - return fn(err); - } - - next(suites.pop()); - }); - } - - next(suites.pop()); -}; - -/** - * Run hooks from the top level down. - * - * @param {String} name - * @param {Function} fn - * @api private - */ - -Runner.prototype.hookUp = function(name, fn){ - var suites = [this.suite].concat(this.parents()).reverse(); - this.hooks(name, suites, fn); -}; - -/** - * Run hooks from the bottom up. - * - * @param {String} name - * @param {Function} fn - * @api private - */ - -Runner.prototype.hookDown = function(name, fn){ - var suites = [this.suite].concat(this.parents()); - this.hooks(name, suites, fn); -}; - -/** - * Return an array of parent Suites from - * closest to furthest. - * - * @return {Array} - * @api private - */ - -Runner.prototype.parents = function(){ - var suite = this.suite - , suites = []; - while (suite = suite.parent) suites.push(suite); - return suites; -}; - -/** - * Run the current test and callback `fn(err)`. - * - * @param {Function} fn - * @api private - */ - -Runner.prototype.runTest = function(fn){ - var test = this.test - , self = this; - - try { - test.on('error', function(err){ - self.fail(test, err); - }); - test.run(fn); - } catch (err) { - fn(err); - } -}; - -/** - * Run tests in the given `suite` and invoke - * the callback `fn()` when complete. - * - * @param {Suite} suite - * @param {Function} fn - * @api private - */ - -Runner.prototype.runTests = function(suite, fn){ - var self = this - , tests = suite.tests - , test; - - function next(err) { - // if we bail after first err - if (self.failures && suite._bail) return fn(); - - // next test - test = tests.shift(); - - // all done - if (!test) return fn(); - - // grep - var match = self._grep.test(test.fullTitle()); - if (self._invert) match = !match; - if (!match) return next(); - - // pending - if (test.pending) { - self.emit('pending', test); - self.emit('test end', test); - return next(); - } - - // execute test and hook(s) - self.emit('test', self.test = test); - self.hookDown('beforeEach', function(){ - self.currentRunnable = self.test; - self.runTest(function(err){ - test = self.test; - - if (err) { - self.fail(test, err); - self.emit('test end', test); - return self.hookUp('afterEach', next); - } - - test.state = 'passed'; - self.emit('pass', test); - self.emit('test end', test); - self.hookUp('afterEach', next); - }); - }); - } - - this.next = next; - next(); -}; - -/** - * Run the given `suite` and invoke the - * callback `fn()` when complete. - * - * @param {Suite} suite - * @param {Function} fn - * @api private - */ - -Runner.prototype.runSuite = function(suite, fn){ - var total = this.grepTotal(suite) - , self = this - , i = 0; - - debug('run suite %s', suite.fullTitle()); - - if (!total) return fn(); - - this.emit('suite', this.suite = suite); - - function next() { - var curr = suite.suites[i++]; - if (!curr) return done(); - self.runSuite(curr, next); - } - - function done() { - self.suite = suite; - self.hook('afterAll', function(){ - self.emit('suite end', suite); - fn(); - }); - } - - this.hook('beforeAll', function(){ - self.runTests(suite, next); - }); -}; - -/** - * Handle uncaught exceptions. - * - * @param {Error} err - * @api private - */ - -Runner.prototype.uncaught = function(err){ - debug('uncaught exception %s', err.message); - var runnable = this.currentRunnable; - if (!runnable || 'failed' == runnable.state) return; - runnable.clearTimeout(); - err.uncaught = true; - this.fail(runnable, err); - - // recover from test - if ('test' == runnable.type) { - this.emit('test end', runnable); - this.hookUp('afterEach', this.next); - return; - } - - // bail on hooks - this.emit('end'); -}; - -/** - * Run the root suite and invoke `fn(failures)` - * on completion. - * - * @param {Function} fn - * @return {Runner} for chaining - * @api public - */ - -Runner.prototype.run = function(fn){ - var self = this - , fn = fn || function(){}; - - debug('start'); - - // uncaught callback - function uncaught(err) { - self.uncaught(err); - } - - // callback - this.on('end', function(){ - debug('end'); - process.removeListener('uncaughtException', uncaught); - fn(self.failures); - }); - - // run suites - this.emit('start'); - this.runSuite(this.suite, function(){ - debug('finished running'); - self.emit('end'); - }); - - // uncaught exception - process.on('uncaughtException', uncaught); - - return this; -}; - -/** - * Filter leaks with the given globals flagged as `ok`. - * - * @param {Array} ok - * @return {Array} - * @api private - */ - -function filterLeaks(ok) { - return filter(keys(global), function(key){ - var matched = filter(ok, function(ok){ - if (~ok.indexOf('*')) return 0 == key.indexOf(ok.split('*')[0]); - return key == ok; - }); - return matched.length == 0 && (!global.navigator || 'onerror' !== key); - }); -} -}); // module: runner.js - -require.register("suite.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var EventEmitter = require('browser/events').EventEmitter - , debug = require('browser/debug')('mocha:suite') - , utils = require('./utils') - , Hook = require('./hook'); - -/** - * Expose `Suite`. - */ - -exports = module.exports = Suite; - -/** - * Create a new `Suite` with the given `title` - * and parent `Suite`. When a suite with the - * same title is already present, that suite - * is returned to provide nicer reporter - * and more flexible meta-testing. - * - * @param {Suite} parent - * @param {String} title - * @return {Suite} - * @api public - */ - -exports.create = function(parent, title){ - var suite = new Suite(title, parent.ctx); - suite.parent = parent; - if (parent.pending) suite.pending = true; - title = suite.fullTitle(); - parent.addSuite(suite); - return suite; -}; - -/** - * Initialize a new `Suite` with the given - * `title` and `ctx`. - * - * @param {String} title - * @param {Context} ctx - * @api private - */ - -function Suite(title, ctx) { - this.title = title; - this.ctx = ctx; - this.suites = []; - this.tests = []; - this.pending = false; - this._beforeEach = []; - this._beforeAll = []; - this._afterEach = []; - this._afterAll = []; - this.root = !title; - this._timeout = 2000; - this._bail = false; -} - -/** - * Inherit from `EventEmitter.prototype`. - */ - -Suite.prototype = new EventEmitter; -Suite.prototype.constructor = Suite; - - -/** - * Return a clone of this `Suite`. - * - * @return {Suite} - * @api private - */ - -Suite.prototype.clone = function(){ - var suite = new Suite(this.title); - debug('clone'); - suite.ctx = this.ctx; - suite.timeout(this.timeout()); - suite.bail(this.bail()); - return suite; -}; - -/** - * Set timeout `ms` or short-hand such as "2s". - * - * @param {Number|String} ms - * @return {Suite|Number} for chaining - * @api private - */ - -Suite.prototype.timeout = function(ms){ - if (0 == arguments.length) return this._timeout; - if (String(ms).match(/s$/)) ms = parseFloat(ms) * 1000; - debug('timeout %d', ms); - this._timeout = parseInt(ms, 10); - return this; -}; - -/** - * Sets whether to bail after first error. - * - * @parma {Boolean} bail - * @return {Suite|Number} for chaining - * @api private - */ - -Suite.prototype.bail = function(bail){ - if (0 == arguments.length) return this._bail; - debug('bail %s', bail); - this._bail = bail; - return this; -}; - -/** - * Run `fn(test[, done])` before running tests. - * - * @param {Function} fn - * @return {Suite} for chaining - * @api private - */ - -Suite.prototype.beforeAll = function(fn){ - if (this.pending) return this; - var hook = new Hook('"before all" hook', fn); - hook.parent = this; - hook.timeout(this.timeout()); - hook.ctx = this.ctx; - this._beforeAll.push(hook); - this.emit('beforeAll', hook); - return this; -}; - -/** - * Run `fn(test[, done])` after running tests. - * - * @param {Function} fn - * @return {Suite} for chaining - * @api private - */ - -Suite.prototype.afterAll = function(fn){ - if (this.pending) return this; - var hook = new Hook('"after all" hook', fn); - hook.parent = this; - hook.timeout(this.timeout()); - hook.ctx = this.ctx; - this._afterAll.push(hook); - this.emit('afterAll', hook); - return this; -}; - -/** - * Run `fn(test[, done])` before each test case. - * - * @param {Function} fn - * @return {Suite} for chaining - * @api private - */ - -Suite.prototype.beforeEach = function(fn){ - if (this.pending) return this; - var hook = new Hook('"before each" hook', fn); - hook.parent = this; - hook.timeout(this.timeout()); - hook.ctx = this.ctx; - this._beforeEach.push(hook); - this.emit('beforeEach', hook); - return this; -}; - -/** - * Run `fn(test[, done])` after each test case. - * - * @param {Function} fn - * @return {Suite} for chaining - * @api private - */ - -Suite.prototype.afterEach = function(fn){ - if (this.pending) return this; - var hook = new Hook('"after each" hook', fn); - hook.parent = this; - hook.timeout(this.timeout()); - hook.ctx = this.ctx; - this._afterEach.push(hook); - this.emit('afterEach', hook); - return this; -}; - -/** - * Add a test `suite`. - * - * @param {Suite} suite - * @return {Suite} for chaining - * @api private - */ - -Suite.prototype.addSuite = function(suite){ - suite.parent = this; - suite.timeout(this.timeout()); - suite.bail(this.bail()); - this.suites.push(suite); - this.emit('suite', suite); - return this; -}; - -/** - * Add a `test` to this suite. - * - * @param {Test} test - * @return {Suite} for chaining - * @api private - */ - -Suite.prototype.addTest = function(test){ - test.parent = this; - test.timeout(this.timeout()); - test.ctx = this.ctx; - this.tests.push(test); - this.emit('test', test); - return this; -}; - -/** - * Return the full title generated by recursively - * concatenating the parent's full title. - * - * @return {String} - * @api public - */ - -Suite.prototype.fullTitle = function(){ - if (this.parent) { - var full = this.parent.fullTitle(); - if (full) return full + ' ' + this.title; - } - return this.title; -}; - -/** - * Return the total number of tests. - * - * @return {Number} - * @api public - */ - -Suite.prototype.total = function(){ - return utils.reduce(this.suites, function(sum, suite){ - return sum + suite.total(); - }, 0) + this.tests.length; -}; - -/** - * Iterates through each suite recursively to find - * all tests. Applies a function in the format - * `fn(test)`. - * - * @param {Function} fn - * @return {Suite} - * @api private - */ - -Suite.prototype.eachTest = function(fn){ - utils.forEach(this.tests, fn); - utils.forEach(this.suites, function(suite){ - suite.eachTest(fn); - }); - return this; -}; - -}); // module: suite.js - -require.register("test.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Runnable = require('./runnable'); - -/** - * Expose `Test`. - */ - -module.exports = Test; - -/** - * Initialize a new `Test` with the given `title` and callback `fn`. - * - * @param {String} title - * @param {Function} fn - * @api private - */ - -function Test(title, fn) { - Runnable.call(this, title, fn); - this.pending = !fn; - this.type = 'test'; -} - -/** - * Inherit from `Runnable.prototype`. - */ - -Test.prototype = new Runnable; -Test.prototype.constructor = Test; - - -}); // module: test.js - -require.register("utils.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var fs = require('browser/fs') - , path = require('browser/path') - , join = path.join - , debug = require('browser/debug')('mocha:watch'); - -/** - * Ignored directories. - */ - -var ignore = ['node_modules', '.git']; - -/** - * Escape special characters in the given string of html. - * - * @param {String} html - * @return {String} - * @api private - */ - -exports.escape = function(html) { - return String(html) - .replace(/&/g, '&') - .replace(/"/g, '"') - .replace(//g, '>'); -}; - -/** - * Array#forEach (<=IE8) - * - * @param {Array} array - * @param {Function} fn - * @param {Object} scope - * @api private - */ - -exports.forEach = function(arr, fn, scope) { - for (var i = 0, l = arr.length; i < l; i++) - fn.call(scope, arr[i], i); -}; - -/** - * Array#indexOf (<=IE8) - * - * @parma {Array} arr - * @param {Object} obj to find index of - * @param {Number} start - * @api private - */ - -exports.indexOf = function (arr, obj, start) { - for (var i = start || 0, l = arr.length; i < l; i++) { - if (arr[i] === obj) - return i; - } - return -1; -}; - -/** - * Array#reduce (<=IE8) - * - * @param {Array} array - * @param {Function} fn - * @param {Object} initial value - * @param {Object} scope - * @api private - */ - -exports.reduce = function(arr, fn, val, scope) { - var rval = val; - - for (var i = 0, l = arr.length; i < l; i++) { - rval = fn.call(scope, rval, arr[i], i, arr); - } - - return rval; -}; - -/** - * Array#filter (<=IE8) - * - * @param {Array} array - * @param {Function} fn - * @param {Object} scope - * @api private - */ - -exports.filter = function(arr, fn, scope) { - var ret = []; - - for (var i = 0, l = arr.length; i < l; i++) { - var val = arr[i]; - if (fn.call(scope, val, i, arr)) - ret.push(val); - } - - return ret; -}; - -/** - * Object.keys (<=IE8) - * - * @param {Object} obj - * @return {Array} keys - * @api private - */ - -exports.keys = Object.keys || function(obj) { - var keys = [] - , has = Object.prototype.hasOwnProperty // for `window` on <=IE8 - - for (var key in obj) { - if (has.call(obj, key)) { - keys.push(key); - } - } - - return keys; -}; - -/** - * Watch the given `files` for changes - * and invoke `fn(file)` on modification. - * - * @param {Array} files - * @param {Function} fn - * @api private - */ - -exports.watch = function(files, fn){ - var options = { interval: 100 }; - files.forEach(function(file){ - debug('file %s', file); - fs.watchFile(file, options, function(curr, prev){ - if (prev.mtime < curr.mtime) fn(file); - }); - }); -}; - -/** - * Ignored files. - */ - -function ignored(path){ - return !~ignore.indexOf(path); -} - -/** - * Lookup files in the given `dir`. - * - * @return {Array} - * @api private - */ - -exports.files = function(dir, ret){ - ret = ret || []; - - fs.readdirSync(dir) - .filter(ignored) - .forEach(function(path){ - path = join(dir, path); - if (fs.statSync(path).isDirectory()) { - exports.files(path, ret); - } else if (path.match(/\.(js|coffee)$/)) { - ret.push(path); - } - }); - - return ret; -}; - -/** - * Compute a slug from the given `str`. - * - * @param {String} str - * @return {String} - * @api private - */ - -exports.slug = function(str){ - return str - .toLowerCase() - .replace(/ +/g, '-') - .replace(/[^-\w]/g, ''); -}; - -/** - * Strip the function definition from `str`, - * and re-indent for pre whitespace. - */ - -exports.clean = function(str) { - str = str - .replace(/^function *\(.*\) *{/, '') - .replace(/\s+\}$/, ''); - - var spaces = str.match(/^\n?( *)/)[1].length - , re = new RegExp('^ {' + spaces + '}', 'gm'); - - str = str.replace(re, ''); - - return str.trim(); -}; - -/** - * Escape regular expression characters in `str`. - * - * @param {String} str - * @return {String} - * @api private - */ - -exports.escapeRegexp = function(str){ - return str.replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&"); -}; -}); // module: utils.js -/** - * Node shims. - * - * These are meant only to allow - * mocha.js to run untouched, not - * to allow running node code in - * the browser. - */ - -process = {}; -process.exit = function(status){}; -process.stdout = {}; -global = window; - -/** - * next tick implementation. - */ - -process.nextTick = (function(){ - // postMessage behaves badly on IE8 - if (window.ActiveXObject || !window.postMessage) { - return function(fn){ fn() }; - } - - // based on setZeroTimeout by David Baron - // - http://dbaron.org/log/20100309-faster-timeouts - var timeouts = [] - , name = 'mocha-zero-timeout' - - window.addEventListener('message', function(e){ - if (e.source == window && e.data == name) { - if (e.stopPropagation) e.stopPropagation(); - if (timeouts.length) timeouts.shift()(); - } - }, true); - - return function(fn){ - timeouts.push(fn); - window.postMessage(name, '*'); - } -})(); - -/** - * Remove uncaughtException listener. - */ - -process.removeListener = function(e){ - if ('uncaughtException' == e) { - window.onerror = null; - } -}; - -/** - * Implements uncaughtException listener. - */ - -process.on = function(e, fn){ - if ('uncaughtException' == e) { - window.onerror = fn; - } -}; - -/** - * Expose mocha. - */ - -window.mocha = require('mocha'); - -// boot -;(function(){ - var utils = mocha.utils - , options = {} - - mocha.suite = new mocha.Suite('', new mocha.Context()); - - /** - * Highlight the given string of `js`. - */ - - function highlight(js) { - return js - .replace(//g, '>') - .replace(/\/\/(.*)/gm, '//$1') - .replace(/('.*?')/gm, '$1') - .replace(/(\d+\.\d+)/gm, '$1') - .replace(/(\d+)/gm, '$1') - .replace(/\bnew *(\w+)/gm, 'new $1') - .replace(/\b(function|new|throw|return|var|if|else)\b/gm, '$1') - } - - /** - * Highlight code contents. - */ - - function highlightCode() { - var code = document.getElementsByTagName('code'); - for (var i = 0, len = code.length; i < len; ++i) { - code[i].innerHTML = highlight(code[i].innerHTML); - } - } - - /** - * Parse the given `qs`. - */ - - function parse(qs) { - return utils.reduce(qs.replace('?', '').split('&'), function(obj, pair){ - var i = pair.indexOf('=') - , key = pair.slice(0, i) - , val = pair.slice(++i); - - obj[key] = decodeURIComponent(val); - return obj; - }, {}); - } - - /** - * Setup mocha with the given setting options. - */ - - mocha.setup = function(opts){ - if ('string' === typeof opts) options.ui = opts; - else options = opts; - - ui = mocha.interfaces[options.ui]; - if (!ui) throw new Error('invalid mocha interface "' + ui + '"'); - if (options.timeout) mocha.suite.timeout(options.timeout); - ui(mocha.suite); - mocha.suite.emit('pre-require', window); - }; - - /** - * Run mocha, returning the Runner. - */ - - mocha.run = function(fn){ - mocha.suite.emit('run'); - var runner = new mocha.Runner(mocha.suite); - var Reporter = options.reporter || mocha.reporters.HTML; - var reporter = new Reporter(runner); - var query = parse(window.location.search || ""); - if (query.grep) runner.grep(new RegExp(query.grep)); - if (options.ignoreLeaks) runner.ignoreLeaks = true; - if (options.globals) runner.globals(options.globals); - runner.globals(['location']); - runner.on('end', highlightCode); - return runner.run(fn); - }; -})(); -})(); \ No newline at end of file diff --git a/test/runner.html b/test/runner.html deleted file mode 100755 index 6130020a1ee..00000000000 --- a/test/runner.html +++ /dev/null @@ -1,33 +0,0 @@ - - - Prebid.js UnitTests - - - - - - - - - - - - - - - - - - -
    - - diff --git a/test/spec/unit/events_spec.js b/test/spec/unit/events_spec.js new file mode 100644 index 00000000000..a904a667cfb --- /dev/null +++ b/test/spec/unit/events_spec.js @@ -0,0 +1,27 @@ +var assert = require("assert"); +var events = require('src/events'); +var utils = require('src/utils'); + +var allEvents = utils._map(require('src/constants.json')['EVENTS'], function (v){ return v; }); +var eventsFired = []; +var event = 'bidAdjustment'; +var _handlers = {}; +var mock = { eventHandler: function() {} }; + +describe('Events', function() { + describe('on', function() { + it() + }); + + describe('emit', function() { + it('should log a message', function() { + events.emit(event); + }); + }); + describe('off', function() { + var event = 'bidAdjustment'; + it('should remove event from _handlers que', function() { + events.off(event); + }); + }); +}); \ No newline at end of file diff --git a/test/spec/utils_spec.js b/test/spec/utils_spec.js index 73d5ae7113a..bed15ff28d4 100755 --- a/test/spec/utils_spec.js +++ b/test/spec/utils_spec.js @@ -39,7 +39,7 @@ describe("Utils", function() { var obj = { 'a' : 'valueA', 'b' : 'valueB' - } + }; var output = utils.getBidIdParamater('a',obj); assert.equal(output,'valueA'); }); @@ -48,7 +48,7 @@ describe("Utils", function() { var obj = { 'a' : 'valueA', 'b' : 'valueB' - } + }; var output = utils.getBidIdParamater('c',obj); assert.equal(output,''); }); @@ -395,6 +395,11 @@ describe("Utils", function() { var output = utils.contains('234','1'); assert.deepEqual(output,false); }); + + it('should return false if the input string is empty', function() { + var output = utils.contains(); + assert.ok(!output, 'an empty string returns false'); + }); }); describe('_map',function(){ @@ -454,9 +459,9 @@ describe("Utils", function() { it('return iframe - marginHeight',function(){ assert.deepEqual(output.marginHeight,'0'); }); - it('return iframe - style.border',function(){ - assert.deepEqual(output.style.border,'0px'); - }); + //it('return iframe - style.border',function(){ + // assert.deepEqual(output.style.border,'0px'); + //}); it('return iframe - scrolling',function(){ assert.deepEqual(output.scrolling,'no'); }); diff --git a/test/test.js b/test/test.js deleted file mode 100644 index 800ec0f6817..00000000000 --- a/test/test.js +++ /dev/null @@ -1,316 +0,0 @@ -var assert = require("assert"); - -/* use this method to test individual files instead of the whole prebid.js project */ - -//TODO refactor to use the spec files -var utils = require('../src/utils'); -var bidmanager = require('../src/bidmanager'); -var appnexus = require('../src/adapters/appnexus'); - - describe('appnexus adapter unit tests', function(){ - - var adapterInstance = null; - var bidRequest = { - bidder: "appnexus", - params: { - memberId : "123", - placementId: "123345", - invCode : 'inv_code', - referrer: "url.com", - alt_referrer: "url.com", - extraParam: "foobar", - somethingElse : 'hello', - query : { - foo : 'bar', - tasty : 'treat' - } - }, - placementCode: "/19968336/header-bid-tag-0", - sizes: [ - [300, 250], - [300, 600] - ] - }; - var callbackId = 'cbId'; - - - it('Get instance', function() { - adapterInstance = appnexus.createNew(); - assert.ok(adapterInstance); - }); - - it('buildJPTCall()', function() { - var expectedUrl = 'http://ib.adnxs.com/jpt?callback=pbjs.handleAnCB&callback_uid=cbId&psa=0&id=123345&member_id=123&code=inv_code&size=300x250&promo_sizes=300x600&foo=bar&tasty=treat&extraParam=foobar&somethingElse=hello&referrer=url.com&alt_referrer=url.com'; - var url = adapterInstance.buildJPTCall(bidRequest, callbackId); - assert.equal(url, expectedUrl); - }); - - - }); - - describe('replaceTokenInString', function(){ - - it('should replace all given tokens in a String', function() { - var tokensToReplace = { - 'foo': 'bar', - 'zap': 'quux' - }; - - var output = utils.replaceTokenInString("hello %FOO%, I am %ZAP%", tokensToReplace, "%"); - assert.equal(output, "hello bar, I am quux"); - }); - - it('should ignore tokens it does not see', function() { - var output = utils.replaceTokenInString("hello %FOO%", {}, "%"); - - assert.equal(output, "hello %FOO%"); - }); - }); - - - describe('bidmanager.js', function(){ - - describe('getKeyValueTargetingPairs', function(){ - var bid = {}; - var bidPriceCpm = 5.578; - var bidPbLg = 5.50; - var bidPbMg = 5.50; - var bidPbHg = 5.57; - var adUnitCode = '12345'; - var bidderCode = 'appnexus'; - var size = '300x250'; - var adId = '1adId'; - - before(function() { - bid.cpm = bidPriceCpm; - bid.pbLg = bidPbLg; - bid.pbMg = bidPbMg; - bid.pbHg = bidPbHg; - bid.height = 300; - bid.width = 250; - bid.adUnitCode = adUnitCode; - bid.getSize = function(){ - return this.height + 'x' + this.width; - }; - bid.bidderCode = bidderCode; - bid.adId = adId; - - }); - - - it('No bidder level configuration defined - default', function() { - var expected = {"hb_bidder": bidderCode, "hb_adid": adId,"hb_pb": bidPbMg,"hb_size": size}; - var response = bidmanager.getKeyValueTargetingPairs(bidderCode, bid); - assert.deepEqual(response, expected); - - }); - - it('Custom configuration for all bidders', function() { - pbjs.bidderSettings = - { - standard: { - adserverTargeting: [{ - key: "hb_bidder", - val: function(bidResponse) { - return bidResponse.bidderCode; - } - }, { - key: "hb_adid", - val: function(bidResponse) { - return bidResponse.adId; - } - }, { - key: "hb_pb", - val: function(bidResponse) { - //change default here - return bidResponse.pbHg; - } - }, { - key: "hb_size", - val: function(bidResponse) { - return bidResponse.size; - - } - }] - - } - }; - - var expected = {"hb_bidder": bidderCode, "hb_adid": adId,"hb_pb": bidPbHg,"hb_size": size}; - var response = bidmanager.getKeyValueTargetingPairs(bidderCode, bid); - assert.deepEqual(response, expected); - - }); - - it('Custom configuration for one bidder', function() { - pbjs.bidderSettings = - { - appnexus: { - adserverTargeting: [{ - key: "hb_bidder", - val: function(bidResponse) { - return bidResponse.bidderCode; - } - }, { - key: "hb_adid", - val: function(bidResponse) { - return bidResponse.adId; - } - }, { - key: "hb_pb", - val: function(bidResponse) { - //change default here - return bidResponse.pbHg; - } - }, { - key: "hb_size", - val: function(bidResponse) { - return bidResponse.size; - - } - }] - - } - }; - - var expected = {"hb_bidder": bidderCode, "hb_adid": adId,"hb_pb": bidPbHg,"hb_size": size}; - var response = bidmanager.getKeyValueTargetingPairs(bidderCode, bid); - assert.deepEqual(response, expected); - - }); - - it('Custom configuration for one bidder - not matched', function() { - pbjs.bidderSettings = - { - nonExistentBidder: { - adserverTargeting: [{ - key: "hb_bidder", - val: function(bidResponse) { - return bidResponse.bidderCode; - } - }, { - key: "hb_adid", - val: function(bidResponse) { - return bidResponse.adId; - } - }, { - key: "hb_pb", - val: function(bidResponse) { - //change default here - return bidResponse.pbHg; - } - }, { - key: "hb_size", - val: function(bidResponse) { - return bidResponse.size; - - } - }] - - } - }; - - var expected = {"hb_bidder": bidderCode, "hb_adid": adId,"hb_pb": bidPbMg,"hb_size": size}; - var response = bidmanager.getKeyValueTargetingPairs(bidderCode, bid); - assert.deepEqual(response, expected); - - }); - - it('Custom bidCpmAdjustment for one bidder and inherit standard', function() { - pbjs.bidderSettings = - { - appnexus: { - bidCpmAdjustment : function(bidCpm){ - return bidCpm * 0.7; - }, - }, - standard: { - adserverTargeting: [{ - key: "hb_bidder", - val: function(bidResponse) { - return bidResponse.bidderCode; - } - }, { - key: "hb_adid", - val: function(bidResponse) { - return bidResponse.adId; - } - }, { - key: "hb_pb", - val: function(bidResponse) { - //change default here - return 10.00; - } - }] - - } - }; - - var expected = {"hb_bidder": bidderCode, "hb_adid": adId,"hb_pb": 10.0 }; - var response = bidmanager.getKeyValueTargetingPairs(bidderCode, bid); - assert.deepEqual(response, expected); - - }); - - it('Custom bidCpmAdjustment AND custom configuration for one bidder and do NOT inherit standard', function() { - pbjs.bidderSettings = - { - appnexus: { - bidCpmAdjustment : function(bidCpm){ - return bidCpm * 0.7; - }, - adserverTargeting: [{ - key: "hb_bidder", - val: function(bidResponse) { - return bidResponse.bidderCode; - } - }, { - key: "hb_adid", - val: function(bidResponse) { - return bidResponse.adId; - } - }, { - key: "hb_pb", - val: function(bidResponse) { - //change default here - return 15.00; - } - }] - }, - standard: { - adserverTargeting: [{ - key: "hb_bidder", - val: function(bidResponse) { - return bidResponse.bidderCode; - } - }, { - key: "hb_adid", - val: function(bidResponse) { - return bidResponse.adId; - } - }, { - key: "hb_pb", - val: function(bidResponse) { - //change default here - return 10.00; - }, - }, - { - key: "hb_size", - val: function(bidResponse) { - return bidResponse.size; - - } - }] - - } - }; - - var expected = {"hb_bidder": bidderCode, "hb_adid": adId,"hb_pb": 15.0 }; - var response = bidmanager.getKeyValueTargetingPairs(bidderCode, bid); - assert.deepEqual(response, expected); - - }); - - }); - }); \ No newline at end of file diff --git a/test/utilsTest.js b/test/utilsTest.js deleted file mode 100644 index d367ab66278..00000000000 --- a/test/utilsTest.js +++ /dev/null @@ -1,28 +0,0 @@ -var assert = require("assert"); - -describe('Utils', function () { - - window = { console: console }; - - var utils = require('../src/utils.js'); - - describe('#replaceTokenInString', function () { - - it('should replace all given tokens in a String', function () { - var tokensToReplace = { - 'foo': 'bar', - 'zap': 'quux' - }; - - var output = utils.replaceTokenInString("hello %FOO%, I am %ZAP%", tokensToReplace, "%"); - - assert.equal(output, "hello bar, I am quux"); - }); - - it('should ignore tokens it does not see', function() { - var output = utils.replaceTokenInString("hello %FOO%", {}, "%"); - - assert.equal(output, "hello %FOO%"); - }); - }) -}); \ No newline at end of file diff --git a/webpack.conf.js b/webpack.conf.js new file mode 100644 index 00000000000..49028e252af --- /dev/null +++ b/webpack.conf.js @@ -0,0 +1,23 @@ +var RewirePlugin = require('rewire-webpack'); +module.exports = { + output: { + filename: 'prebid.js' + }, + resolve: { + modulesDirectories: ['', 'node_modules', 'src', 'adapters'] + }, + resolveLoader: { + modulesDirectories: ['loaders', 'node_modules'] + }, + module: { + loaders: [ + { + test: /\.json$/, + loader: 'json' + } + ] + }, + plugins: [ + new RewirePlugin() + ] +}; From 1ee05bbab5fc071eebe7aab99493bf426b99028b Mon Sep 17 00:00:00 2001 From: Matt Date: Wed, 3 Feb 2016 16:12:24 -0500 Subject: [PATCH 009/160] Adding adapter for TripleLift exchange and bidder params for pbjs adUnits --- integrationExamples/gpt/pbjs_example_gpt.html | 6 + src/adapters/triplelift.js | 120 ++++++++++++++++++ 2 files changed, 126 insertions(+) create mode 100644 src/adapters/triplelift.js diff --git a/integrationExamples/gpt/pbjs_example_gpt.html b/integrationExamples/gpt/pbjs_example_gpt.html index 24196e5aa51..1e8eb2d704f 100644 --- a/integrationExamples/gpt/pbjs_example_gpt.html +++ b/integrationExamples/gpt/pbjs_example_gpt.html @@ -106,6 +106,12 @@ cp: 521732, ct: 76835 } + }, + { + bidder: 'triplelift', + params: { + inventoryCode: 'TO ADD' + } } ] },{ diff --git a/src/adapters/triplelift.js b/src/adapters/triplelift.js new file mode 100644 index 00000000000..d70242c22d3 --- /dev/null +++ b/src/adapters/triplelift.js @@ -0,0 +1,120 @@ +var CONSTANTS = require('../constants.json'); +var utils = require('../utils.js'); +var adloader = require('../adloader.js'); +var bidmanager = require('../bidmanager.js'); +var bidfactory = require('../bidfactory.js'); + +/* TripleLift bidder factory function +* Use to create a TripleLiftAdapter object +*/ + + +var TripleLiftAdapter = function TripleLiftAdapter() { + +function _callBids(params) { + var tlReq = params.bids; + var bidsCount = tlReq.length; + + //set expected bids count for callback execution + bidmanager.setExpectedBidsCount('triplelift',bidsCount); + + for (var i = 0; i < bidsCount; i++) { + var bidReqeust = tlReq[i]; + var callbackId = utils.getUniqueIdentifierStr(); + adloader.loadScript(buildTLCall(bidReqeust, callbackId)); + //store a reference to the bidRequest from the callback id + bidmanager.pbCallbackMap[callbackId] = bidReqeust; + } + +} + + +function buildTLCall(bid, callbackId) { + //determine tag params + var inventoryCode = utils.getBidIdParamater('inventoryCode', bid.params); + + //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; + + tlCall = utils.tryAppendQueryString(tlCall, 'callback', 'pbjs.TLCB'); + tlCall = utils.tryAppendQueryString(tlCall, 'lib', 'prebid'); + tlCall = utils.tryAppendQueryString(tlCall, 'lib', '0.5.0'); + tlCall = utils.tryAppendQueryString(tlCall, 'callback_id', callbackId); + tlCall = utils.tryAppendQueryString(tlCall, 'inv_code', inventoryCode); + + //sizes takes a bit more logic + var sizeQueryString = utils.parseSizesInput(bid.sizes); + if (sizeQueryString) { + tlCall += sizeQueryString + '&'; + } + + //append referrer + var referrer = utils.getTopWindowUrl(); + tlCall = utils.tryAppendQueryString(tlCall, 'referrer', referrer); + + //remove the trailing "&" + if (tlCall.lastIndexOf('&') === tlCall.length - 1) { + tlCall = tlCall.substring(0, tlCall.length - 1); + } + + // @if NODE_ENV='debug' + utils.logMessage('tlCall request built: ' + tlCall); + // @endif + + //append a timer here to track latency + bid.startTime = new Date().getTime(); + + return tlCall; + +} + + +//expose the callback to the global object: +pbjs.TLCB = function(tlResponseObj) { + if (tlResponseObj && tlResponseObj.callback_id) { + var bidObj = bidmanager.pbCallbackMap[tlResponseObj.callback_id], + placementCode = bidObj.placementCode; + + // @if NODE_ENV='debug' + utils.logMessage('JSONP callback function called for inventory code: ' + bidObj.params.inventoryCode); + // @endif + + var bid = []; + if (tlResponseObj && tlResponseObj.cpm && tlResponseObj.cpm !== 0) { + + bid = bidfactory.createBid(1); + bid.bidderCode = 'triplelift'; + bid.cpm = tlResponseObj.cpm; + bid.ad = tlResponseObj.ad; + bid.width = tlResponseObj.width; + bid.height = tlResponseObj.height; + bid.dealId = tlResponseObj.deal_id; + bidmanager.addBidResponse(placementCode, bid); + + } else { + //no response data + // @if NODE_ENV='debug' + utils.logMessage('No prebid response from TripleLift for inventory code: ' + bidObj.params.inventoryCode); + // @endif + bid = bidfactory.createBid(2); + bid.bidderCode = 'triplelift'; + bidmanager.addBidResponse(placementCode, bid); + } + + } else { + //no response data + // @if NODE_ENV='debug' + utils.logMessage('No prebid response for placement %%PLACEMENT%%'); + // @endif + + } + +}; + +return { + callBids: _callBids + +}; +}; +module.exports = TripleLiftAdapter; From 60d4a3cd629961c4389d12cffd8676b0b7a1d097 Mon Sep 17 00:00:00 2001 From: protonate Date: Fri, 5 Feb 2016 10:31:36 -0800 Subject: [PATCH 010/160] update README with reinstall note --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index edf19edc35f..803808918e3 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ +# HEY DEVELOPERS! +### `rm -rf ./node_modules && npm cache clean && npm install` +With [this commit](http://bit.ly/1Ran76T) we have changed the build system to use Webpack, Karma and Istanbul. This change was made to support improved unit test coverage and reporting. Reinstalling Prebid.js is necessary as many node modules changed, and you are likely to experience errors otherwise. After pulling down latest master please `rm -rf ./node_modules && npm cache clean && npm install`. Prebid.js ======== From 3f2d70b4d2db6d4e38ece46efe623049bd3af250 Mon Sep 17 00:00:00 2001 From: protonate Date: Fri, 5 Feb 2016 12:09:33 -0800 Subject: [PATCH 011/160] update README with build path change info --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 803808918e3..30b9bd42199 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # HEY DEVELOPERS! ### `rm -rf ./node_modules && npm cache clean && npm install` With [this commit](http://bit.ly/1Ran76T) we have changed the build system to use Webpack, Karma and Istanbul. This change was made to support improved unit test coverage and reporting. Reinstalling Prebid.js is necessary as many node modules changed, and you are likely to experience errors otherwise. After pulling down latest master please `rm -rf ./node_modules && npm cache clean && npm install`. +### Build path change +The `./dist` and `./dev` directories have been moved to a `./build` directory -- please update your own dev and example paths to `prebid.js` accordingly. You will also find code coverage reports in the `./build/coverage` directory. Prebid.js ======== From 126b9ec059e0a4244963211f2bc909503bddc575 Mon Sep 17 00:00:00 2001 From: protonate Date: Mon, 8 Feb 2016 10:15:27 -0800 Subject: [PATCH 012/160] include `del` in package.json --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 067e79df0d3..ccefee07a7e 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "chai": "^3.3.0", "chai-as-promised": "^5.1.0", "clone": "^0.1.17", + "del": "^2.2.0", "es5-shim": "^4.5.2", "eslint": "^1.9.0", "express": "^4.10.2", From 0d52eed995d50492389b1d25468c89e3febb7b72 Mon Sep 17 00:00:00 2001 From: Matt Kendall Date: Mon, 8 Feb 2016 15:52:53 -0500 Subject: [PATCH 013/160] Update `utils.parseSizesInput` to be more generic --- src/adapters/appnexus.js | 24 +++++++++++++++++++++++- src/utils.js | 29 ++++++----------------------- test/spec/utils_spec.js | 8 ++++---- 3 files changed, 33 insertions(+), 28 deletions(-) diff --git a/src/adapters/appnexus.js b/src/adapters/appnexus.js index 2dbefc16077..56d28c9648a 100644 --- a/src/adapters/appnexus.js +++ b/src/adapters/appnexus.js @@ -111,7 +111,29 @@ var AppNexusAdapter = function AppNexusAdapter() { //sizes takes a bit more logic - var sizeQueryString = utils.parseSizesInput(bid.sizes); + var sizeQueryString = ''; + var parsedSizes = utils.parseSizesInput(bid.sizes); + + //combine string into proper querystring for impbus + var 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 (var 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); + } + } + } + + console.log(sizeQueryString); + if (sizeQueryString) { jptCall += sizeQueryString + '&'; } diff --git a/src/utils.js b/src/utils.js index ef8f5f91317..0d3e9e21f28 100644 --- a/src/utils.js +++ b/src/utils.js @@ -109,10 +109,12 @@ exports.extend = function(target, source){ return target; }; -//parse a GPT-Style General Size Array or a string like "300x250" into a format -//suitable for passing to a GPT tag, may include size and/or promo sizes +/** + * Parse a GPT-Style general size Array like `[[300, 250]]` or `"300x250,970x90"` into an array of sizes `["300x250"]` or '['300x250', '970x90']' + * @param {array[array|number]} sizeObj Input array or double array [300,250] or [[300,250], [728,90]] + * @return {array[string]} Array of strings like `["300x250"]` or `["300x250", "728x90"]` + */ exports.parseSizesInput = function(sizeObj) { - var sizeQueryString; var parsedSizes = []; //if a string for now we can assume it is a single size, like "300x250" @@ -146,26 +148,7 @@ exports.parseSizesInput = function(sizeObj) { } } - - //combine string into proper querystring for impbus - var 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 (var 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); - } - } - } - - return sizeQueryString; + return parsedSizes; }; diff --git a/test/spec/utils_spec.js b/test/spec/utils_spec.js index bed15ff28d4..c6425473b2e 100755 --- a/test/spec/utils_spec.js +++ b/test/spec/utils_spec.js @@ -162,25 +162,25 @@ describe("Utils", function() { it('should return query string using multi size array',function(){ var sizes = [[728, 90], [970, 90]]; var output = utils.parseSizesInput(sizes); - assert.equal(output,'size=728x90&promo_sizes=970x90'); + assert.deepEqual(output,['728x90', '970x90']); }); it('should return query string using single size array',function(){ var sizes = [728, 90]; var output = utils.parseSizesInput(sizes); - assert.equal(output,'size=728x90'); + assert.deepEqual(output,['728x90']); }); it('should return query string using string input',function(){ var sizes = '300x250,970x90'; var output = utils.parseSizesInput(sizes); - assert.equal(output,'size=300x250&promo_sizes=970x90'); + assert.deepEqual(output,['300x250', '970x90']); }); it('return undefined if input array is empty',function(){ var sizes =[]; var output = utils.parseSizesInput(sizes); - assert.equal(output,undefined); + assert.deepEqual(output,[]); }); }); From cc4ea6a703258b00be75c33f44526b9aa1b8d809 Mon Sep 17 00:00:00 2001 From: Matt Kendall Date: Mon, 8 Feb 2016 16:13:59 -0500 Subject: [PATCH 014/160] Remove console.log --- src/adapters/appnexus.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/adapters/appnexus.js b/src/adapters/appnexus.js index 56d28c9648a..de139fafac8 100644 --- a/src/adapters/appnexus.js +++ b/src/adapters/appnexus.js @@ -132,8 +132,6 @@ var AppNexusAdapter = function AppNexusAdapter() { } } - console.log(sizeQueryString); - if (sizeQueryString) { jptCall += sizeQueryString + '&'; } From 5b09dce1e0daf3f9441032574339978565e0099c Mon Sep 17 00:00:00 2001 From: Matt Kendall Date: Mon, 8 Feb 2016 16:14:47 -0500 Subject: [PATCH 015/160] Add a default to rubicon adapter for sizes in case nothing is specified. --- src/adapters/rubicon.js | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/adapters/rubicon.js b/src/adapters/rubicon.js index 6b5570c4a8a..ab7316b5b32 100644 --- a/src/adapters/rubicon.js +++ b/src/adapters/rubicon.js @@ -153,6 +153,30 @@ var RubiconAdapter = function RubiconAdapter() { adloader.loadScript(RUBICONTAG_URL + accountId + '.js', done); } + /** + * map the sizes in `bid.sizes` to Rubicon specific keys + * @param {object} array of bids + * @return {[type]} [description] + */ + function _mapSizes(bids){ + utils._each(bids, function(bid){ + if(bid.params.sizes){ + return; + } + //return array like ['300x250', '728x90'] + var parsedSizes = utils.parseSizesInput(bid.sizes); + //iterate the bid.sizes array to lookup codes + var tempSize = []; + for(var i =0; i < parsedSizes.length; i++){ + var rubiconKey = RUBICON_SIZE_MAP[parsedSizes[i]]; + if(rubiconKey){ + tempSize.push(rubiconKey); + } + } + bid.params.sizes = tempSize; + }); + } + /** * Define the slot using the rubicontag.defineSlot API * @param {Object} Bidrequest @@ -195,6 +219,8 @@ var RubiconAdapter = function RubiconAdapter() { // even just loading the SDK _bidStart = (new Date).getTime(); + _mapSizes(params.bids); + utils._each(params.bids, function (bid, index) { // on the first bid, set up the SDK // the config will be set on each bid From 12182a48db25cf264f710ca079ddcfe671e23585 Mon Sep 17 00:00:00 2001 From: Matt Kendall Date: Mon, 8 Feb 2016 16:17:24 -0500 Subject: [PATCH 016/160] Moved the old rubicon adapter to `rubiconLegacy.js` --- src/adapters/rubiconLegacy.js | 191 ++++++++++++++++++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 src/adapters/rubiconLegacy.js diff --git a/src/adapters/rubiconLegacy.js b/src/adapters/rubiconLegacy.js new file mode 100644 index 00000000000..d6fd04c0f6c --- /dev/null +++ b/src/adapters/rubiconLegacy.js @@ -0,0 +1,191 @@ +//Factory for creating the bidderAdaptor +var CONSTANTS = require('../constants.json'); +var utils = require('../utils.js'); +var bidfactory = require('../bidfactory.js'); +var bidmanager = require('../bidmanager.js'); + +/** + * This adapter is deprecated and should not be used. Please use rubicon.js instead + */ +var RubiconAdapter = function RubiconAdapter() { + // Map size dimensions to size 'ID' + var sizeMap = {}; + + function callBids(params) { + var bidArr = params.bids; + for (var i = 0; i < bidArr.length; i++) { + var bid = bidArr[i]; + //get the first size in the array + //TODO validation + var width = bid.sizes[0][0]; + var height = bid.sizes[0][1]; + var iframeContents = createRequestContent(bid, 'window.parent.pbjs.handleRubiconCallback', width, height); + var iframeId = loadIframeContent(iframeContents); + bid.iframeId = iframeId; + bidmanager.pbCallbackMap[getBidId(bid)] = bid; + } + + } + + // Build an ID that can be used to identify the response to the bid request. There + // may be an identifier we can send that gets sent back to us. + function getBidId(bid) { + return (bid.params ? [bid.params.rp_account, bid.params.rp_site, bid.params.rp_zonesize] : + [bid.account_id, bid.site_id, bid.zone_id, bid.size_id]).join('-'); + + } + + function loadIframeContent(content, callback) { + //create the iframe + var iframe = utils.createInvisibleIframe(); + var elToAppend = document.getElementsByTagName('head')[0]; + //insert the iframe into document + elToAppend.insertBefore(iframe, elToAppend.firstChild); + //todo make this more browser friendly + var iframeDoc = iframe.contentWindow.document; + iframeDoc.write(content); + iframeDoc.close(); + + return iframe.id; + + } + + function createRequestContent(bidOptions, callback, width, height) { + + // Map the size 'ID' to the dimensions + sizeMap[bidOptions.params.rp_zonesize.split('-')[1]] = { + width: width, + height: height + }; + + var content = 'inDapIF=true;'; + content += ''; + content += ''; + + + content += '' + + 'window.rp_account = "%%RP_ACCOUNT%%";' + + 'window.rp_site = "%%RP_SITE%%";' + + 'window.rp_zonesize = "%%RP_ZONESIZE%%";' + + 'window.rp_tracking = "%%RP_TRACKING%%";' + + 'window.rp_visitor = %%RP_VISITOR%%;' + + 'window.rp_width = %%RP_WIDTH%%;' + + 'window.rp_height = %%RP_HEIGHT%%;' + + 'window.rp_adtype = "jsonp";' + + 'window.rp_inventory = %%RP_INVENTORY%% ;' + + 'window.rp_floor=%%RP_FLOOR%%;' + + 'window.rp_fastlane = true;' + + 'window.rp_callback = ' + callback + ';'; + + + var map = {}; + map['RP_ACCOUNT'] = bidOptions.params.rp_account; + map['RP_SITE'] = bidOptions.params.rp_site; + map['RP_ZONESIZE'] = bidOptions.params.rp_zonesize; + map['RP_TRACKING'] = (bidOptions.params.rp_tracking) ? bidOptions.params.rp_tracking : ''; + map['RP_VISITOR'] = bidOptions.params.rp_visitor ? bidOptions.params.rp_visitor : '{}'; + map['RP_WIDTH'] = width; + map['RP_HEIGHT'] = height; + map['RP_INVENTORY'] = bidOptions.params.rp_inventory || '{}'; + map['RP_FLOOR'] = bidOptions.params.rp_floor ? bidOptions.params.rp_floor : '0.00'; + + content += ''; + content += ''; + content += ''; + + content = utils.replaceTokenInString(content, map, '%%'); + + //console.log(content); + + return content; + + } + + window.pbjs = window.pbjs || {que: []}; + window.pbjs.handleRubiconCallback = function(response) { + var placementCode = ''; + + var bid = {}; + if (response && response.status === 'ok') { + try { + var iframeId = ''; + var bidObj = bidmanager.getPlacementIdByCBIdentifer(getBidId(response)); + if (bidObj) { + placementCode = bidObj.placementCode; + bidObj.status = CONSTANTS.STATUS.GOOD; + iframeId = bidObj.iframeId; + } + + if (response.ads && response.ads[0] && response.ads[0].status === 'ok') { + bid = bidfactory.createBid(1); + + var rubiconAd = response.ads[0]; + var size = sizeMap[rubiconAd.size_id]; + var width = 0; + var height = 0; + + var iframeObj = window.frames[iframeId]; + var rubiconObj; + if(iframeObj.contentWindow){ + rubiconObj = iframeObj.contentWindow.RubiconAdServing + }else{ + rubiconObj = iframeObj.window.RubiconAdServing; + } + + if (rubiconObj && rubiconObj.AdSizes) { + /* should return + 1: { + dim: "468x60" + }, + */ + size = rubiconObj.AdSizes[rubiconAd.size_id]; + var sizeArray = size.dim.split('x'); + width = sizeArray[0]; + height = sizeArray[1]; + } + + bid.cpm = rubiconAd.cpm; + bid.ad = ''; + bid.ad_id = rubiconAd.ad_id; + bid.bidderCode = 'rubicon'; + bid.sizeId = rubiconAd.size_id; + bid.width = width; + bid.height = height; + + }else{ + bid = bidfactory.createBid(2); + bid.bidderCode = 'rubicon'; + var bidObj = bidmanager.getPlacementIdByCBIdentifer(getBidId(response)); + if (bidObj) { + placementCode = bidObj.placementCode; + } + } + + } catch (e) { + utils.logError('Error parsing rubicon response bid: ' + e.message); + } + + } else { + //set bid response code to 2 = no response or error + bid = bidfactory.createBid(2); + bid.bidderCode = 'rubicon'; + var bidObj = bidmanager.getPlacementIdByCBIdentifer(getBidId(response)); + if (bidObj) { + placementCode = bidObj.placementCode; + } + + } + + //add the bid response here + bidmanager.addBidResponse(placementCode, bid); + + }; + + return { + callBids: callBids + + }; + //end of Rubicon bid adaptor +}; + +module.exports = RubiconAdapter; \ No newline at end of file From 1c1ea7b88932677a2530f75797b98d6d71d411d2 Mon Sep 17 00:00:00 2001 From: Kevin Baragona Date: Wed, 13 Jan 2016 14:41:17 -0800 Subject: [PATCH 017/160] Sovrn adapter - add no bid reponses for all placements that are not mentioned in the Sovrn bid response --- src/adapters/sovrn.js | 47 +++++++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/src/adapters/sovrn.js b/src/adapters/sovrn.js index 85df2b382cd..682df269d92 100644 --- a/src/adapters/sovrn.js +++ b/src/adapters/sovrn.js @@ -4,7 +4,7 @@ var bidfactory = require('../bidfactory.js'); var bidmanager = require('../bidmanager.js'); var adloader = require('../adloader'); -var defaultPlacementForBadBid = ''; +var allPlacementCodes; /** * Adapter for requesting bids from Sovrn @@ -40,9 +40,7 @@ var SovrnAdapter = function SovrnAdapter() { var page = window.location.pathname + location.search + location.hash; var sovrnImps = []; - //assign the first adUnit (placement) for bad bids; - defaultPlacementForBadBid = bidReqs[0].placementCode; - + allPlacementCodes = []; //build impression array for sovrn utils._each(bidReqs, function(bid) { @@ -74,6 +72,7 @@ var SovrnAdapter = function SovrnAdapter() { }; sovrnImps.push(imp); bidmanager.pbCallbackMap[imp.id] = bid; + allPlacementCodes.push(bid.placementCode); }); // build bid request with impressions @@ -91,24 +90,41 @@ var SovrnAdapter = function SovrnAdapter() { adloader.loadScript(scriptUrl, null); } + function addBlankBidResponsesForAllPlacementsExceptThese(placementsWithBidsBack){ + utils._each(allPlacementCodes, function(placementCode) + { + if(utils.contains(placementsWithBidsBack, placementCode)) { + // A bid was returned for this placement already + } else { + // Add a no-bid response for this placement. + var bid = {}; + bid = bidfactory.createBid(2); + bid.bidderCode = 'sovrn'; + bidmanager.addBidResponse(placementCode, bid); + } + }); + } + + //expose the callback to the global object: pbjs.sovrnResponse = function(sovrnResponseObj) { - var bid = {}; // valid object? if (sovrnResponseObj && sovrnResponseObj.id) { // valid object w/ bid responses? if (sovrnResponseObj.seatbid && sovrnResponseObj.seatbid.length !==0 && sovrnResponseObj.seatbid[0].bid && sovrnResponseObj.seatbid[0].bid.length !== 0) { - + var placementsWithBidsBack = []; sovrnResponseObj.seatbid[0].bid.forEach(function(sovrnBid){ var responseCPM; var placementCode = ''; var id = sovrnBid.impid; + var bid = {}; // try to fetch the bid request we sent Sovrn - var bidObj = bidmanager.getPlacementIdByCBIdentifer(id); + var bidObj = bidmanager.getPlacementIdByCBIdentifer(id); if (bidObj){ placementCode = bidObj.placementCode; + placementsWithBidsBack.push(placementCode); bidObj.status = CONSTANTS.STATUS.GOOD; //place ad response on bidmanager._adResponsesByBidderId @@ -145,7 +161,7 @@ var SovrnAdapter = function SovrnAdapter() { bidmanager.addBidResponse(placementCode, bid); - } else { + } else { //0 price bid //indicate that there is no bid for this placement bid = bidfactory.createBid(2); @@ -153,24 +169,21 @@ var SovrnAdapter = function SovrnAdapter() { bidmanager.addBidResponse(placementCode, bid); } - } else { // bid not found, we never asked for this? + } else { // bid not found, we never asked for this? //no response data bid = bidfactory.createBid(2); bid.bidderCode = 'sovrn'; bidmanager.addBidResponse(placementCode, bid); } }); + addBlankBidResponsesForAllPlacementsExceptThese(placementsWithBidsBack); } else { - //no response data - bid = bidfactory.createBid(2); - bid.bidderCode = 'sovrn'; - bidmanager.addBidResponse(defaultPlacementForBadBid, bid); + //no response data for any placements + addBlankBidResponsesForAllPlacementsExceptThese([]); } } else { - //no response data - bid = bidfactory.createBid(2); - bid.bidderCode = 'sovrn'; - bidmanager.addBidResponse(defaultPlacementForBadBid, bid); + //no response data for any placements + addBlankBidResponsesForAllPlacementsExceptThese([]); } }; // sovrnResponse From 7e3a81b942651817c2eee55436d505a3cd806f93 Mon Sep 17 00:00:00 2001 From: JohnBuonomo Date: Fri, 29 Jan 2016 14:14:30 -0500 Subject: [PATCH 018/160] springserve adapter springserve adapter test params springserve adapter - test setup; small fix springserve adapter get's sizes from ad unit update test params recover sizes from bidrequest springserve adapter indentation --- integrationExamples/gpt/pbjs_example_gpt.html | 8 ++ src/adaptermanager.js | 2 + src/adapters/springserve.js | 105 ++++++++++++++++++ 3 files changed, 115 insertions(+) create mode 100644 src/adapters/springserve.js diff --git a/integrationExamples/gpt/pbjs_example_gpt.html b/integrationExamples/gpt/pbjs_example_gpt.html index 24196e5aa51..217589c062a 100644 --- a/integrationExamples/gpt/pbjs_example_gpt.html +++ b/integrationExamples/gpt/pbjs_example_gpt.html @@ -106,6 +106,14 @@ cp: 521732, ct: 76835 } + }, + { + bidder: 'springserve', + params: { + impId: 1234, + supplyPartnerId: 1, + test: true // only include when testing + } } ] },{ diff --git a/src/adaptermanager.js b/src/adaptermanager.js index 54fd6229ba8..8e07b42a843 100644 --- a/src/adaptermanager.js +++ b/src/adaptermanager.js @@ -10,6 +10,7 @@ var YieldbotAdapter = require('./adapters/yieldbot'); var IndexExchange = require('./adapters/indexExchange'); var Sovrn = require('./adapters/sovrn'); var PulsePointAdapter = require('./adapters/pulsepoint.js'); +var SpringServeAdapter = require('./adapters/springserve.js'); var bidmanager = require('./bidmanager.js'); var utils = require('./utils.js'); var CONSTANTS = require('./constants.json'); @@ -96,6 +97,7 @@ this.registerBidAdapter(PubmaticAdapter(), 'pubmatic'); this.registerBidAdapter(CriteoAdapter(), 'criteo'); this.registerBidAdapter(YieldbotAdapter(), 'yieldbot'); this.registerBidAdapter(IndexExchange(), 'indexExchange'); +this.registerBidAdapter(SpringServeAdapter(), 'springserve'); this.registerBidAdapter(Sovrn(),'sovrn'); this.registerBidAdapter(AolAdapter(), 'aol'); this.registerBidAdapter(PulsePointAdapter(),'pulsepoint'); diff --git a/src/adapters/springserve.js b/src/adapters/springserve.js new file mode 100644 index 00000000000..c37c80cdaf7 --- /dev/null +++ b/src/adapters/springserve.js @@ -0,0 +1,105 @@ +var CONSTANTS = require('../constants.json'); +var bidfactory = require('../bidfactory.js'); +var bidmanager = require('../bidmanager.js'); +var adloader = require('../adloader'); + +var SpringServeAdapter = function SpringServeAdapter() { + + function buildSpringServeCall(bid){ + + var spCall = "http://bidder.springserve.com/display/hbid?"; + + //get width and height from bid attribute + var size = bid.sizes[0]; + var width = size[0]; + var height = size[1]; + + spCall += "&w="; + spCall += width; + spCall += "&h="; + spCall += height; + + var params = bid.params; + //maps param attributes to request parameters + var requestAttrMap = { + sp: 'supplyPartnerId', + imp_id: 'impId' + }; + + for (var property in requestAttrMap){ + if(requestAttrMap.hasOwnProperty && params.hasOwnProperty(requestAttrMap[property])){ + spCall += '&'; + spCall += property; + spCall += '='; + //get property from params and include it in request + spCall += params[requestAttrMap[property]]; + } + } + + var domain = window.location.hostname; + + //override domain when testing + if(params.hasOwnProperty("test") && params["test"] === true){ + spCall += "&debug=true"; + domain = "test.com"; + } + + spCall += "&domain="; + spCall += domain; + spCall += "&callback=pbjs.handleSpringServeCB"; + + return spCall + } + + function _callBids(params){ + bids = params.bids || []; + for (var i = 0; i < bids.length; i++) { + var bid = bids[i]; + bidmanager.pbCallbackMap[bid.params.impId] = params; + adloader.loadScript(buildSpringServeCall(bid)) + } + } + + pbjs.handleSpringServeCB = function(responseObj){ + if(responseObj && responseObj.seatbid && responseObj.seatbid.length > 0 && + responseObj.seatbid[0].bid[0] !== undefined){ + //look up the request attributs stored in the bidmanager + var responseBid = responseObj.seatbid[0].bid[0]; + var requestObj = bidmanager.getPlacementIdByCBIdentifer(responseBid.impid); + var bid = bidfactory.createBid(1); + + //assign properties from the original request to the bid object + for (var i = 0; i < requestObj.bids.length; i++ ){ + var bidRequest = requestObj.bids[i]; + if(bidRequest.bidder = "springserve"){ + var placementCode = bidRequest.placementCode; + var size = bidRequest.sizes[0]; + bid.width = size[0]; + bid.height = size[1]; + } + } + + bid.bidderCode = requestObj.bidderCode; + + if(responseBid.hasOwnProperty('price') && responseBid.hasOwnProperty('adm')){ + //assign properties from the response to the bid object + bid.cpm = responseBid.price; + bid.ad = responseBid.adm; + } else { + //make object for invalid bid response + bid = bidfactory.createBid(2); + bid.bidderCode = 'springserve'; + } + bidmanager.addBidResponse(placementCode, bid); + } + } + + // Export the callBids function, so that prebid.js can execute this function + // when the page asks to send out bid requests. + return { + callBids: _callBids, + buildSpringServeCall : buildSpringServeCall + }; +}; + +module.exports = SpringServeAdapter; From 5023ff9d5149e5256e72eb920fac441091c872bc Mon Sep 17 00:00:00 2001 From: protonate Date: Mon, 8 Feb 2016 15:11:18 -0800 Subject: [PATCH 019/160] remove criteo adapter --- integrationExamples/gpt/pbjs_example_gpt.html | 8 +- .../gpt/pbjs_partial_refresh_gpt.html | 6 -- src/adaptermanager.js | 2 - src/adapters/criteo.js | 74 ------------------- 4 files changed, 1 insertion(+), 89 deletions(-) delete mode 100644 src/adapters/criteo.js diff --git a/integrationExamples/gpt/pbjs_example_gpt.html b/integrationExamples/gpt/pbjs_example_gpt.html index 6e192a7ad66..9b42f75405f 100644 --- a/integrationExamples/gpt/pbjs_example_gpt.html +++ b/integrationExamples/gpt/pbjs_example_gpt.html @@ -38,7 +38,7 @@ (function() { var d = document, pbs = d.createElement("script"), pro = d.location.protocal; pbs.type = "text/javascript"; - pbs.src = '/build/dist/prebid.js'; + pbs.src = '/build/dist/prebid.js'; var target = document.getElementsByTagName("head")[0]; target.insertBefore(pbs, target.firstChild); })(); @@ -80,12 +80,6 @@ publisherId: 'TO ADD', adSlot: 'TO ADD@300x600' } - }, { - bidder: 'criteo', - params: { - nid: "TO ADD", - cookiename: "cto_test" - } }, { bidder: 'yieldbot', params: { diff --git a/integrationExamples/gpt/pbjs_partial_refresh_gpt.html b/integrationExamples/gpt/pbjs_partial_refresh_gpt.html index 1cf631e640c..4830adddae1 100644 --- a/integrationExamples/gpt/pbjs_partial_refresh_gpt.html +++ b/integrationExamples/gpt/pbjs_partial_refresh_gpt.html @@ -82,12 +82,6 @@ publisherId: 'TO ADD', adSlot: 'TO ADD@300x600' } - }, { - bidder: 'criteo', - params: { - nid: "TO ADD", - cookiename: "cto_test" - } }] },{ code: '/9968336/header-bid-tag1', diff --git a/src/adaptermanager.js b/src/adaptermanager.js index 54fd6229ba8..4038fce81f2 100644 --- a/src/adaptermanager.js +++ b/src/adaptermanager.js @@ -5,7 +5,6 @@ var AppNexusAdapter = require('./adapters/appnexus.js'); var AolAdapter = require('./adapters/aol'); var OpenxAdapter = require('./adapters/openx'); var PubmaticAdapter = require('./adapters/pubmatic.js'); -var CriteoAdapter = require('./adapters/criteo'); var YieldbotAdapter = require('./adapters/yieldbot'); var IndexExchange = require('./adapters/indexExchange'); var Sovrn = require('./adapters/sovrn'); @@ -93,7 +92,6 @@ this.registerBidAdapter(RubiconAdapter(), 'rubicon'); this.registerBidAdapter(AppNexusAdapter.createNew(), 'appnexus'); this.registerBidAdapter(OpenxAdapter(), 'openx'); this.registerBidAdapter(PubmaticAdapter(), 'pubmatic'); -this.registerBidAdapter(CriteoAdapter(), 'criteo'); this.registerBidAdapter(YieldbotAdapter(), 'yieldbot'); this.registerBidAdapter(IndexExchange(), 'indexExchange'); this.registerBidAdapter(Sovrn(),'sovrn'); diff --git a/src/adapters/criteo.js b/src/adapters/criteo.js deleted file mode 100644 index 5da825396f7..00000000000 --- a/src/adapters/criteo.js +++ /dev/null @@ -1,74 +0,0 @@ -var CONSTANTS = require('../constants.json'); -var utils = require('../utils.js'); -var bidfactory = require('../bidfactory.js'); -var bidmanager = require('../bidmanager.js'); -var adloader = require('../adloader'); - -/** - * Adapter for requesting bids from Criteo. - * - * @returns {{callBids: _callBids}} - * @constructor - */ -var CriteoAdapter = function CriteoAdapter() { - var bids; - - function _callBids(params) { - bids = params.bids || []; - - // Only make one request per "nid" - _getUniqueNids(bids).forEach(_requestBid); - } - - function _getUniqueNids(bids) { - var key; - var map = {}; - var nids = []; - bids.forEach(function(bid) { - map[bid.params.nid] = bid; - }); - for (key in map) { - if (map.hasOwnProperty(key)) { - nids.push(map[key]); - } - } - return nids; - } - - function _requestBid(bid) { - var varname = bid.params.varname; - var scriptUrl = '//rtax.criteo.com/delivery/rta/rta.js?netId=' + encodeURI(bid.params.nid) + - '&cookieName=' + encodeURI(bid.params.cookiename) + - '&rnd=' + Math.floor(Math.random() * 99999999999) + - '&varName=' + encodeURI(varname); - - adloader.loadScript(scriptUrl, function(response) { - var adResponse; - var content = window[varname]; - - // Add a response for each bid matching the "nid" - bids.forEach(function(existingBid) { - if (existingBid.params.nid === bid.params.nid) { - if (content) { - adResponse = bidfactory.createBid(1); - adResponse.bidderCode = 'criteo'; - - adResponse.keys = content.replace(/\;$/, '').split(';'); - } else { - // Indicate an ad was not returned - adResponse = bidfactory.createBid(2); - adResponse.bidderCode = 'criteo'; - } - - bidmanager.addBidResponse(existingBid.placementCode, adResponse); - } - }); - }); - } - - return { - callBids: _callBids - }; -}; - -module.exports = CriteoAdapter; From ed5e5c4faac71f5c805b6f048cea5b28ea4f6a42 Mon Sep 17 00:00:00 2001 From: Matt Kendall Date: Tue, 9 Feb 2016 11:27:24 -0500 Subject: [PATCH 020/160] Bake in bidder 'brealtime' as a default. --- src/adaptermanager.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/adaptermanager.js b/src/adaptermanager.js index 77c7086ef2f..4e77281af21 100644 --- a/src/adaptermanager.js +++ b/src/adaptermanager.js @@ -99,3 +99,5 @@ this.registerBidAdapter(SpringServeAdapter(), 'springserve'); this.registerBidAdapter(Sovrn(),'sovrn'); this.registerBidAdapter(AolAdapter(), 'aol'); this.registerBidAdapter(PulsePointAdapter(),'pulsepoint'); +//default bidder alias +this.aliasBidAdapter('appnexus', 'brealtime'); From b0021f5dea6f3acb582a9dd6ae8c0157349a7f70 Mon Sep 17 00:00:00 2001 From: Matt Kendall Date: Tue, 9 Feb 2016 11:29:43 -0500 Subject: [PATCH 021/160] version bump --- bower.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bower.json b/bower.json index ec994aee553..66503f3b5ce 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "prebid-js", - "version": "0.4.1", + "version": "0.6.0", "authors": [ "Matt Kendall", "Paul Yang" diff --git a/package.json b/package.json index ccefee07a7e..cd24be13d0c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "0.5.0", + "version": "0.6.0", "description": "Header Bidding Management Library", "main": "prebid.js", "scripts": { From 7be91713557f3bc0db66be0b828049d6cfddbbeb Mon Sep 17 00:00:00 2001 From: Matt Kendall Date: Tue, 9 Feb 2016 11:39:37 -0500 Subject: [PATCH 022/160] Remove latency tracking from appnexus. --- src/adapters/appnexus.js | 65 ---------------------------------------- 1 file changed, 65 deletions(-) diff --git a/src/adapters/appnexus.js b/src/adapters/appnexus.js index de139fafac8..3508b6b39ae 100644 --- a/src/adapters/appnexus.js +++ b/src/adapters/appnexus.js @@ -9,38 +9,6 @@ var AppNexusAdapter = function AppNexusAdapter() { var baseAdapter = Adapter.createNew('appnexus'); var isCalled = false; - //time tracking buckets, to be used to track latency within script - //array index is timeslice in ms, value passed to buildTrackingTag() is impbus tracker id - var timeTrackingBuckets = []; - timeTrackingBuckets[100] = buildTrackingTag(21139); - timeTrackingBuckets[200] = buildTrackingTag(21140); - timeTrackingBuckets[300] = buildTrackingTag(21141); - timeTrackingBuckets[400] = buildTrackingTag(21142); - timeTrackingBuckets[500] = buildTrackingTag(21143); - timeTrackingBuckets[600] = buildTrackingTag(21144); - timeTrackingBuckets[700] = buildTrackingTag(21145); - timeTrackingBuckets[800] = buildTrackingTag(21146); - timeTrackingBuckets[1000] = buildTrackingTag(21147); - timeTrackingBuckets[1300] = buildTrackingTag(21148); - timeTrackingBuckets[1600] = buildTrackingTag(21149); - timeTrackingBuckets[2000] = buildTrackingTag(21150); - timeTrackingBuckets[5000] = buildTrackingTag(21151); - timeTrackingBuckets[10000] = buildTrackingTag(21152); - - //over 10.000 tracker - var timeTrackerOverMaxBucket = buildTrackingTag(21154); - //var timeTrackerBidTimeout = buildTrackingTag(19432); - - //generic bid requeted tracker - var timeTrackerBidRequested = buildTrackingTag(21153); - - // var timeTrackerBidRequested = buildTrackingTag(19435); - - //helper function to construct impbus trackers - function buildTrackingTag(id) { - return 'https://secure.adnxs.com/imptr?id=' + id + '&t=2'; - } - baseAdapter.callBids = function(params){ var bidCode = baseAdapter.getBidderCode(); @@ -59,33 +27,6 @@ var AppNexusAdapter = function AppNexusAdapter() { } }; - //given a starttime and an end time, hit the correct impression tracker - function processAndTrackLatency(startTime, endTime, placementCode) { - - if (startTime && endTime) { - //get the difference between times - var timeDiff = endTime - startTime; - var trackingPixelFound = false; - var trackingUrl = ''; - for (var curTrackerItem in timeTrackingBuckets) { - //find the closest upper bound of defined tracking times - if (timeDiff <= curTrackerItem) { - trackingPixelFound = true; - trackingUrl = timeTrackingBuckets[curTrackerItem]; - adloader.trackPixel(trackingUrl); - break; - } - } - //if we didn't find a bucket, assume use the catch-all time over bucket - if (!trackingPixelFound) { - trackingUrl = timeTrackerOverMaxBucket; - adloader.trackPixel(trackingUrl); - } - - utils.logMessage('latency for placement code : ' + placementCode + ' : ' + timeDiff + ' ms.' + ' Tracking URL Fired : ' + trackingUrl); - } - } - function buildJPTCall(bid, callbackId) { @@ -205,12 +146,6 @@ var AppNexusAdapter = function AppNexusAdapter() { placementCode = bidObj.placementCode; //set the status bidObj.status = CONSTANTS.STATUS.GOOD; - //track latency - try { - processAndTrackLatency(bidObj.startTime, new Date().getTime(), placementCode); - } catch (e) {} - - //place ad response on bidmanager._adResponsesByBidderId } // @if NODE_ENV='debug' From 8a76b8b756156ac7db5f144d50875e9df2033e37 Mon Sep 17 00:00:00 2001 From: Matt Kendall Date: Wed, 10 Feb 2016 15:57:18 -0500 Subject: [PATCH 023/160] Add member to Appnexus adapter. --- src/adapters/appnexus.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/adapters/appnexus.js b/src/adapters/appnexus.js index 3508b6b39ae..a29dacf6f43 100644 --- a/src/adapters/appnexus.js +++ b/src/adapters/appnexus.js @@ -32,7 +32,9 @@ var AppNexusAdapter = function AppNexusAdapter() { //determine tag params var placementId = utils.getBidIdParamater('placementId', bid.params); + //memberId will be deprecated, use member instead var memberId = utils.getBidIdParamater('memberId', bid.params); + var member = utils.getBidIdParamater('member', bid.params); var inventoryCode = utils.getBidIdParamater('invCode', bid.params); var query = utils.getBidIdParamater('query', bid.params); var referrer = utils.getBidIdParamater('referrer', bid.params); @@ -46,7 +48,13 @@ var AppNexusAdapter = function AppNexusAdapter() { jptCall = utils.tryAppendQueryString(jptCall, 'callback_uid', callbackId); jptCall = utils.tryAppendQueryString(jptCall, 'psa', '0'); jptCall = utils.tryAppendQueryString(jptCall, 'id', placementId); - jptCall = utils.tryAppendQueryString(jptCall, 'member_id', memberId); + if(member){ + jptCall = utils.tryAppendQueryString(jptCall, 'member_id', member); + }else if(memberId){ + jptCall = utils.tryAppendQueryString(jptCall, 'member_id', memberId); + utils.logMessage('appnexus.callBids: "memberId" will be deprecated soon. Please use "member" instead'); + } + jptCall = utils.tryAppendQueryString(jptCall, 'code', inventoryCode); jptCall = utils.tryAppendQueryString(jptCall, 'code', inventoryCode); @@ -94,6 +102,7 @@ var AppNexusAdapter = function AppNexusAdapter() { delete paramsCopy.query; delete paramsCopy.referrer; delete paramsCopy.alt_referrer; + delete paramsCopy.member; //get the reminder var queryParams = utils.parseQueryStringParameters(paramsCopy); From 87d2a7775f7e1cee202b5ec034ee7c16c5de5f4c Mon Sep 17 00:00:00 2001 From: Jurij Sinickij Date: Fri, 13 Nov 2015 14:23:45 +0200 Subject: [PATCH 024/160] Adform adapter added --- .../gpt/pbjs_example_adform_gpt.html | 182 ++++++++++++++++++ src/adaptermanager.js | 7 + src/adapters/adform.js | 149 ++++++++++++++ 3 files changed, 338 insertions(+) create mode 100644 integrationExamples/gpt/pbjs_example_adform_gpt.html create mode 100644 src/adapters/adform.js diff --git a/integrationExamples/gpt/pbjs_example_adform_gpt.html b/integrationExamples/gpt/pbjs_example_adform_gpt.html new file mode 100644 index 00000000000..a8faba37503 --- /dev/null +++ b/integrationExamples/gpt/pbjs_example_adform_gpt.html @@ -0,0 +1,182 @@ + + +Adform Prebid.js example + + + + + +
    + +
    + + + + diff --git a/src/adaptermanager.js b/src/adaptermanager.js index 4e77281af21..0893e4f340f 100644 --- a/src/adaptermanager.js +++ b/src/adaptermanager.js @@ -10,6 +10,8 @@ var IndexExchange = require('./adapters/indexExchange'); var Sovrn = require('./adapters/sovrn'); var PulsePointAdapter = require('./adapters/pulsepoint.js'); var SpringServeAdapter = require('./adapters/springserve.js'); +var AdformAdapter = require('./adapters/adform'); +var Aol = require('./adapters/aol'); var bidmanager = require('./bidmanager.js'); var utils = require('./utils.js'); var CONSTANTS = require('./constants.json'); @@ -95,9 +97,14 @@ this.registerBidAdapter(OpenxAdapter(), 'openx'); this.registerBidAdapter(PubmaticAdapter(), 'pubmatic'); this.registerBidAdapter(YieldbotAdapter(), 'yieldbot'); this.registerBidAdapter(IndexExchange(), 'indexExchange'); +<<<<<<< HEAD this.registerBidAdapter(SpringServeAdapter(), 'springserve'); this.registerBidAdapter(Sovrn(),'sovrn'); this.registerBidAdapter(AolAdapter(), 'aol'); this.registerBidAdapter(PulsePointAdapter(),'pulsepoint'); //default bidder alias this.aliasBidAdapter('appnexus', 'brealtime'); +======= +this.registerBidAdapter(Aol(), 'aol'); +this.registerBidAdapter(AdformAdapter(), 'adform'); +>>>>>>> f8cd017... Adform adapter added diff --git a/src/adapters/adform.js b/src/adapters/adform.js new file mode 100644 index 00000000000..a346dc3fdbf --- /dev/null +++ b/src/adapters/adform.js @@ -0,0 +1,149 @@ +var utils = require('../utils.js'); +var adloader = require('../adloader.js'); +var bidmanager = require('../bidmanager.js'); +var bidfactory = require('../bidfactory.js'); + +function AdformAdapter() { + + return { + callBids: _callBids + }; + + function _callBids(params) { + var callbackName = '_adf_' + utils.getUniqueIdentifierStr(), bid, noDomain = true; + var bids = params.bids; + var request = []; + + for (var i = 0, l = bids.length; i < l; i++) { + bid = bids[i]; + if (bid.adxDomain && noDomain) { + noDomain = false; + request.unshift('//' + bid.adxDomain + '/adx/?rp=4'); + } + request.push(formRequestUrl(bid.params)); + } + + if (noDomain) { + request.unshift('//adx.adform.net/adx/?rp=4'); + } + + pbjs[callbackName] = handleCallback(bids); + request.push('callback=pbjs.' + callbackName); + + adloader.loadScript(request.join('&')); + } + + function formRequestUrl(reqData) { + var key; + var url = []; + + var validProps = [ + 'mid', 'inv', 'pdom', 'mname', 'mkw', 'mkv', 'cat', 'bcat', 'bcatrt', 'adv', 'advt', 'cntr', 'cntrt', 'maxp', + 'minp', 'sminp', 'w', 'h', 'pb', 'pos', 'cturl', 'iturl', 'cttype', 'hidedomain', 'cdims', 'test' + ]; + + for (var i = 0, l = validProps.length; i < l; i++) { + key = validProps[i]; + if (reqData.hasOwnProperty(key)) + url.push(key, '=', reqData[key], '&'); + } + + return encode64(url.join('')); + } + + function handleCallback(bids) { + return function handleResponse(adItems) { + var bidObject, bidder = 'adform', adItem, bid; + for(var i = 0, l = adItems.length; i < l; i++){ + adItem = adItems[i]; + bid = bids[i]; + if (adItem && adItem.response === 'banner' && + verifySize(adItem, bid.sizes)) { + + bidObject = bidfactory.createBid(1); + bidObject.bidderCode = bidder; + bidObject.cpm = adItem.win_bid; + bidObject.cur = adItem.win_cur; + bidObject.ad = adItem.banner; + bidObject.width = adItem.width; + bidObject.height = adItem.height; + bidmanager.addBidResponse(bid.placementCode, bidObject); + } else { + bidObject = bidfactory.createBid(2); + bidObject.bidderCode = bidder; + bidmanager.addBidResponse(bid.placementCode, bidObject); + } + } + }; + + function verifySize(adItem, validSizes) { + for (var j = 0, k = validSizes.length; j < k; j++) { + if (adItem.width === validSizes[j][0] && + adItem.height === validSizes[j][1]) { + return true; + } + } + return false; + } + } + + function encode64(input) { + var out = []; + var chr1, chr2, chr3, enc1, enc2, enc3, enc4; + var i = 0; + var _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_="; + + input = utf8_encode(input); + + while (i < input.length) { + + chr1 = input.charCodeAt(i++); + chr2 = input.charCodeAt(i++); + chr3 = input.charCodeAt(i++); + + enc1 = chr1 >> 2; + enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); + enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); + enc4 = chr3 & 63; + + if (isNaN(chr2)) { + enc3 = enc4 = 64; + } else if (isNaN(chr3)) { + enc4 = 64; + } + out.push(_keyStr.charAt(enc1), _keyStr.charAt(enc2)); + if (enc3 != 64) + out.push(_keyStr.charAt(enc3)); + if (enc4 != 64) + out.push(_keyStr.charAt(enc4)); + } + + return out.join(''); + } + + function utf8_encode(string) { + string = string.replace(/\r\n/g, "\n"); + var utftext = ""; + + for (var n = 0; n < string.length; n++) { + + var c = string.charCodeAt(n); + + if (c < 128) { + utftext += String.fromCharCode(c); + } else if ((c > 127) && (c < 2048)) { + utftext += String.fromCharCode((c >> 6) | 192); + utftext += String.fromCharCode((c & 63) | 128); + } else { + utftext += String.fromCharCode((c >> 12) | 224); + utftext += String.fromCharCode(((c >> 6) & 63) | 128); + utftext += String.fromCharCode((c & 63) | 128); + } + } + + return utftext; + } + +} + +module.exports = AdformAdapter; From baa333f8c1a48e0c60de2dee1adce800a9503c4c Mon Sep 17 00:00:00 2001 From: protonate Date: Wed, 10 Feb 2016 14:33:36 -0800 Subject: [PATCH 025/160] resolve merge conflicts --- src/adaptermanager.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/adaptermanager.js b/src/adaptermanager.js index 0893e4f340f..8d6d8a24719 100644 --- a/src/adaptermanager.js +++ b/src/adaptermanager.js @@ -97,14 +97,11 @@ this.registerBidAdapter(OpenxAdapter(), 'openx'); this.registerBidAdapter(PubmaticAdapter(), 'pubmatic'); this.registerBidAdapter(YieldbotAdapter(), 'yieldbot'); this.registerBidAdapter(IndexExchange(), 'indexExchange'); -<<<<<<< HEAD this.registerBidAdapter(SpringServeAdapter(), 'springserve'); this.registerBidAdapter(Sovrn(),'sovrn'); this.registerBidAdapter(AolAdapter(), 'aol'); this.registerBidAdapter(PulsePointAdapter(),'pulsepoint'); //default bidder alias this.aliasBidAdapter('appnexus', 'brealtime'); -======= this.registerBidAdapter(Aol(), 'aol'); this.registerBidAdapter(AdformAdapter(), 'adform'); ->>>>>>> f8cd017... Adform adapter added From c98d702bf8d7e7f39384f45ef35e418c8fa57bfe Mon Sep 17 00:00:00 2001 From: protonate Date: Wed, 10 Feb 2016 15:06:10 -0800 Subject: [PATCH 026/160] update integration examples pages --- .../gpt/pbjs_example_adform_gpt.html | 182 ------------------ integrationExamples/gpt/pbjs_example_gpt.html | 12 +- 2 files changed, 11 insertions(+), 183 deletions(-) delete mode 100644 integrationExamples/gpt/pbjs_example_adform_gpt.html diff --git a/integrationExamples/gpt/pbjs_example_adform_gpt.html b/integrationExamples/gpt/pbjs_example_adform_gpt.html deleted file mode 100644 index a8faba37503..00000000000 --- a/integrationExamples/gpt/pbjs_example_adform_gpt.html +++ /dev/null @@ -1,182 +0,0 @@ - - -Adform Prebid.js example - - - - - -
    - -
    - - - - diff --git a/integrationExamples/gpt/pbjs_example_gpt.html b/integrationExamples/gpt/pbjs_example_gpt.html index 4a8ffb1abaf..3be8da52457 100644 --- a/integrationExamples/gpt/pbjs_example_gpt.html +++ b/integrationExamples/gpt/pbjs_example_gpt.html @@ -80,7 +80,17 @@ publisherId: 'TO ADD', adSlot: 'TO ADD@300x600' } - }, { + }, + { + bidder: 'adform', + // available params: [ 'mid', 'inv', 'pdom', 'mname', 'mkw', 'mkv', 'cat', 'bcat', 'bcatrt', 'adv', 'advt', 'cntr', 'cntrt', 'maxp', 'minp', 'sminp', 'w', 'h', 'pb', 'pos', 'cturl', 'iturl', 'cttype', 'hidedomain', 'cdims', 'test' ] + params: { + adxDomain: 'adx.adform.net', //optional + mid: 74042, + test: 1 + } + }, + { bidder: 'yieldbot', params: { psn: 'TO ADD', From db9f2ef4f57bb7d3bfdc565b996abdca183ba0ae Mon Sep 17 00:00:00 2001 From: Eric Perez Date: Mon, 1 Feb 2016 12:06:30 -0700 Subject: [PATCH 027/160] Updated adloader.js: simplified the 'trackPixel' function: exit early if pixelUrl was not passed in, fixed bug whereby pixelUrl will always evaluate to 'truthy' because when it's not passed in, it's undefined and is cast to a string 'undefined' and then the "'&rnd=' + Math.random()" piece is appended to it; multiplied random number by 100 to ensure only positive integer values. Changed TypeError to utils.logMessage in exports.trackPixel; added ternary indexOf check for '?' in 'pixelUrl' to remove the assumption that there are already query parameter(s) present; added return value so that exports.trackPixel becomes testable + unit tests to test said functionality. --- src/adloader.js | 33 +++++++++++++------------------ test/spec/adloader_spec.js | 40 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 19 deletions(-) create mode 100644 test/spec/adloader_spec.js diff --git a/src/adloader.js b/src/adloader.js index 746211a50c0..7531a05232a 100644 --- a/src/adloader.js +++ b/src/adloader.js @@ -1,7 +1,7 @@ var utils = require('./utils'); //add a script tag to the page, used to add /jpt call to page exports.loadScript = function(tagSrc, callback) { - if(!tagSrc){ + if(!tagSrc) { utils.logError('Error attempting to request empty URL', 'adloader.js:loadScript'); return; } @@ -38,23 +38,18 @@ exports.loadScript = function(tagSrc, callback) { } }; +//track a impbus tracking pixel +//TODO: Decide if tracking via AJAX is sufficent, or do we need to +//run impression trackers via page pixels? exports.trackPixel = function(pixelUrl) { - //track a impbus tracking pixel - - //TODO: Decide of tracking via AJAX is sufficent, or do we need to - //run impression trackers via page pixels? - try { - - //add a cachebuster so we don't end up dropping any impressions - pixelUrl += '&rnd=' + Math.random(); - - if (pixelUrl) { - var img = document.createElement('img'); - img.src = pixelUrl; - } - - - } catch (e) { - + var delimiter, trackingPixel; + if (!pixelUrl || typeof(pixelUrl) !== 'string') { + utils.logMessage('Missing or invalid pixelUrl.'); + return; } -}; \ No newline at end of file + delimiter = pixelUrl.indexOf('?') > 0 ? '&' : '?'; + //add a cachebuster so we don't end up dropping any impressions + trackingPixel = pixelUrl + delimiter + 'rnd=' + Math.floor(Math.random() * 1E7); + (new Image()).src = trackingPixel; + return trackingPixel; +}; diff --git a/test/spec/adloader_spec.js b/test/spec/adloader_spec.js new file mode 100644 index 00000000000..0ff86b35dc0 --- /dev/null +++ b/test/spec/adloader_spec.js @@ -0,0 +1,40 @@ +describe('adLoader', function() { + var assert = require('chai').assert, + adLoader = require('../../src/adloader'); + + describe('trackPixel', function() { + it('correctly appends a cachebuster query paramter to a pixel with no existing parameters', function() { + var inputUrl = "http://www.example.com/tracking_pixel", + token = '?rnd=', + expectedPartialUrl = inputUrl + token, + actual = adLoader.trackPixel(inputUrl), + actualPartialUrl = actual.split(token)[0] + token, + randomNumber = parseInt(actual.split(token)[1]); + assert.strictEqual(actualPartialUrl, expectedPartialUrl); + assert.isNumber(randomNumber); + }); + }); + + it('correctly appends a cachebuster query paramter to a pixel with one existing parameter', function() { + var inputUrl = "http://www.example.com/tracking_pixel?food=bard", + token = '&rnd=', + expectedPartialUrl = inputUrl + token, + actual = adLoader.trackPixel(inputUrl), + actualPartialUrl = actual.split(token)[0] + token, + randomNumber = parseInt(actual.split(token)[1]); + assert.strictEqual(actualPartialUrl, expectedPartialUrl); + assert.isNumber(randomNumber); + }); + + it('correctly appends a cachebuster query paramter to a pixel with multiple existing parameters', function() { + var inputUrl = "http://www.example.com/tracking_pixel?food=bard&zing=zang", + token = '&rnd=', + expectedPartialUrl = inputUrl + token, + actual = adLoader.trackPixel(inputUrl), + actualPartialUrl = actual.split(token)[0] + token, + randomNumber = parseInt(actual.split(token)[1]); + assert.strictEqual(actualPartialUrl, expectedPartialUrl); + assert.isNumber(randomNumber); + }); + +}); From 027f6753e8c77940a5a640f231018b0d03828b2c Mon Sep 17 00:00:00 2001 From: Iheb KHEMISSI Date: Thu, 11 Feb 2016 09:52:12 +0000 Subject: [PATCH 028/160] Aol adapter is included twice in the Adapter manager --- src/adaptermanager.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/adaptermanager.js b/src/adaptermanager.js index 8d6d8a24719..4d7522c719b 100644 --- a/src/adaptermanager.js +++ b/src/adaptermanager.js @@ -11,7 +11,6 @@ var Sovrn = require('./adapters/sovrn'); var PulsePointAdapter = require('./adapters/pulsepoint.js'); var SpringServeAdapter = require('./adapters/springserve.js'); var AdformAdapter = require('./adapters/adform'); -var Aol = require('./adapters/aol'); var bidmanager = require('./bidmanager.js'); var utils = require('./utils.js'); var CONSTANTS = require('./constants.json'); @@ -61,7 +60,7 @@ exports.registerBidAdapter = function(bidAdaptor, bidderCode) { } else { utils.logError('Bidder adaptor error for bidder code: ' + bidderCode + 'bidder must implement a callBids() function'); } - + } else { utils.logError('bidAdaptor or bidderCode not specified'); } @@ -103,5 +102,4 @@ this.registerBidAdapter(AolAdapter(), 'aol'); this.registerBidAdapter(PulsePointAdapter(),'pulsepoint'); //default bidder alias this.aliasBidAdapter('appnexus', 'brealtime'); -this.registerBidAdapter(Aol(), 'aol'); this.registerBidAdapter(AdformAdapter(), 'adform'); From 683c49ba7cca3a9881a74fe40ed538a7d628f1e9 Mon Sep 17 00:00:00 2001 From: Bart van Bragt Date: Thu, 11 Feb 2016 12:12:29 +0100 Subject: [PATCH 029/160] Add a pixel to allow OpenX to record revenue --- src/adapters/openx.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/adapters/openx.js b/src/adapters/openx.js index b835b08c612..41afb39bc2f 100644 --- a/src/adapters/openx.js +++ b/src/adapters/openx.js @@ -77,7 +77,16 @@ var OpenxAdapter = function OpenxAdapter(options) { adResponse.bidderCode = 'openx'; adResponse.ad_id = adUnit.get('ad_id'); adResponse.cpm = Number(adUnit.get('pub_rev')) / 1000; + adResponse.ad = adUnit.get('html'); + // 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'); @@ -103,4 +112,4 @@ var OpenxAdapter = function OpenxAdapter(options) { }; }; -module.exports = OpenxAdapter; \ No newline at end of file +module.exports = OpenxAdapter; From b4cb05739ddea7b97577e2d355ea399c248b8ccb Mon Sep 17 00:00:00 2001 From: Matt Kendall Date: Thu, 11 Feb 2016 09:48:19 -0500 Subject: [PATCH 030/160] Fix bug with refreshes with Rubicon Adapter. Update example page --- integrationExamples/gpt/pbjs_example_gpt.html | 30 ++++++++++------ .../gpt/pbjs_partial_refresh_gpt.html | 20 +++++++---- src/adapters/rubicon.js | 35 ++++++++++++++++--- 3 files changed, 64 insertions(+), 21 deletions(-) diff --git a/integrationExamples/gpt/pbjs_example_gpt.html b/integrationExamples/gpt/pbjs_example_gpt.html index 3be8da52457..51964a23cda 100644 --- a/integrationExamples/gpt/pbjs_example_gpt.html +++ b/integrationExamples/gpt/pbjs_example_gpt.html @@ -144,12 +144,20 @@ },{ bidder: 'rubicon', params: { - rp_account : 'TO ADD', - rp_site: 'TO ADD', - rp_zonesize : 'TO ADD', - rp_tracking : 'TO ADD', - rp_inventory : 'TO ADD', - rp_floor : '0.1' + accountId: "1234", //String - required + siteId: "1234", //String - required + zoneId: "1234", //String - required + sizes: [15], //Array[Number] - optional + userId: "12345", //String - optional + keywords: ["a","b","c"], //Array[String] - optional + inventory: { //Object - optional + rating:"5-star", + prodtype:"tech" + }, + visitor: { //Object - optional + ucat:"new", + search:"iphone" + } } },{ bidder: 'yieldbot', @@ -161,11 +169,11 @@ { bidder: 'indexExchange', params: { - id: 'TO ADD', //String - required - siteID: TO ADD, //Number - required - timeout: TO ADD, //Number - Optional - tier2SiteID: TO ADD, //Number - Optional - tier3SiteID: TO ADD //Number - Optional + id: 'TO ADD', //String - required + siteID: 123, //Number - required + timeout: 456, //Number - Optional + tier2SiteID: 789, //Number - Optional + tier3SiteID: 1234 //Number - Optional } }] }]; diff --git a/integrationExamples/gpt/pbjs_partial_refresh_gpt.html b/integrationExamples/gpt/pbjs_partial_refresh_gpt.html index 4830adddae1..b183ba91adc 100644 --- a/integrationExamples/gpt/pbjs_partial_refresh_gpt.html +++ b/integrationExamples/gpt/pbjs_partial_refresh_gpt.html @@ -101,12 +101,20 @@ },{ bidder: 'rubicon', params: { - rp_account : 'TO ADD', - rp_site: 'TO ADD', - rp_zonesize : 'TO ADD', - rp_tracking : 'TO ADD', - rp_inventory : 'TO ADD', - rp_floor : '0.1' + accountId: "1234", //String - required + siteId: "1234", //String - required + zoneId: "1234", //String - required + sizes: [15], //Array[Number] - optional + userId: "12345", //String - optional + keywords: ["a","b","c"], //Array[String] - optional + inventory: { //Object - optional + rating:"5-star", + prodtype:"tech" + }, + visitor: { //Object - optional + ucat:"new", + search:"iphone" + } } },{ bidder: 'yieldbot', diff --git a/src/adapters/rubicon.js b/src/adapters/rubicon.js index ab7316b5b32..2c57b2d48ee 100644 --- a/src/adapters/rubicon.js +++ b/src/adapters/rubicon.js @@ -8,7 +8,7 @@ var adloader = require('../adloader'); /** * @class RubiconAdapter - * Prebid adapter for Rubicon's Rubicon + * Prebid adapter for Rubicon's header bidding client */ var RubiconAdapter = function RubiconAdapter() { var RUBICONTAG_URL = (window.location.protocol) + '//ads.rubiconproject.com/header/'; @@ -23,6 +23,7 @@ var RubiconAdapter = function RubiconAdapter() { "300x1050": 54, "970x250": 57 }; + var RUBICON_INITIALIZED = 0; // the fastlane creative code var RUBICON_CREATIVE_START = ''; + + // pre-initialize the rubicon object + // needs to be attached to the window + window.rubicontag = window.rubicontag || {}; + window.rubicontag.cmd = window.rubicontag.cmd || []; + + // timestamp for logging + var _bidStart = null; + var bidCount = 0; + + /** + * Create an error bid + * @param {String} placement - the adunit path + * @param {Object} response - the (error) response from fastlane + * @return {Bid} a bid, for prebid + */ + function _errorBid(response, ads) { + var bidResponse = bidfactory.createBid(2); + bidResponse.bidderCode = RUBICON_BIDDER_CODE; + + // use the raw ads as the 'error' + bidResponse.error = ads; + return bidResponse; + } + + /** + * Sort function for CPM + * @param {Object} adA + * @param {Object} adB + * @return {Float} sort order value + */ + function _adCpmSort(adA, adB) { + return (adB.cpm || 0.0) - (adA.cpm || 0.0); + } + + /** + * Produce the code to render a creative + * @param {String} elemId the element passed to rubicon; this is essentially the ad-id + * @param {Array} size array of width, height + * @return {String} creative + */ + function _creative(elemId, size) { + + // convert the size to a rubicon sizeId + var sizeId = RUBICON_SIZE_MAP[size.join('x')]; + + if (!sizeId) { + utils.logError( + 'fastlane: missing sizeId for size: ' + size.join('x') + ' could not render creative', + RUBICON_BIDDER_CODE, RUBICON_SIZE_MAP); + return ''; + } + + return RUBICON_CREATIVE_START + elemId + '", "' + sizeId + RUBICON_CREATIVE_END; + } + + /** + * Create a (successful) bid for a unit, + * based on the given response + * @param {String} placement placement code/unit path + * @param {Object} response the response from rubicon + * @return {Bid} a bid objectj + */ + function _makeBid(response, ads) { + + // if there are multiple ads, sort by CPM + ads = ads.sort(_adCpmSort); + + var bidResponse = bidfactory.createBid(1), + ad = ads[0], + size = ad.dimensions; + + if (!size) { + // this really shouldn't happen + utils.logError('no dimensions given', RUBICON_BIDDER_CODE, ad); + return _errorBid(response, ads); + } + + bidResponse.bidderCode = RUBICON_BIDDER_CODE; + bidResponse.cpm = ad.cpm; + // the element id is what the iframe will use to render + // itself using the rubicontag.renderCreative API + bidResponse.ad = _creative(response.getElementId(), size); + bidResponse.width = size[0]; + bidResponse.height = size[1]; + return bidResponse; + } + + /** + * Add a success/error bid based + * on the response from rubicon + * @param {Object} response -- AJAX response from fastlane + */ + function _addBid(response, ads) { + // get the bid for the placement code + var bid; + if (!ads || ads.length === 0) { + bid = _errorBid(response, ads); + } else { + bid = _makeBid(response, ads); + } + + bidmanager.addBidResponse(response.getSlotName(), bid); + } + + /** + * Helper to queue functions on rubicontag + * ready/available + * @param {Function} callback + */ + function _rready(callback) { + window.rubicontag.cmd.push(callback); + } + + /** + * download the rubicontag sdk + * @param {Object} options + * @param {String} options.accountId + * @param {Function} callback + */ + function _initSDK(options, done) { + if (RUBICON_INITIALIZED) return; + RUBICON_INITIALIZED = 1; + var accountId = options.accountId; + adloader.loadScript(RUBICONTAG_URL + accountId + '.js', done); + } + + /** + * map the sizes in `bid.sizes` to Rubicon specific keys + * @param {object} array of bids + * @return {[type]} [description] + */ + function _mapSizes(bids){ + utils._each(bids, function(bid){ + if(bid.params.sizes){ + return; + } + //return array like ['300x250', '728x90'] + var parsedSizes = utils.parseSizesInput(bid.sizes); + //iterate the bid.sizes array to lookup codes + var tempSize = []; + for(var i =0; i < parsedSizes.length; i++){ + var rubiconKey = RUBICON_SIZE_MAP[parsedSizes[i]]; + if(rubiconKey){ + tempSize.push(rubiconKey); + } + } + bid.params.sizes = tempSize; + }); + } + + /** + * Define the slot using the rubicontag.defineSlot API + * @param {Object} Bidrequest + */ + function _defineSlot(bid) { + _rready(function () { + var newSlot=window.rubicontag.defineSlot({ + siteId: bid.params.siteId, + zoneId: bid.params.zoneId, + id: bid.placementCode, + sizes: bid.params.sizes + }); + if (bid.params.position) { + newSlot.setPosition(bid.params.position); + } + if (bid.params.userId) { + window.rubicontag.setUserKey(bid.params.userId); + } + if (bid.params.keywords) { + for(var i=0; i < bid.params.keywords.length; i++){ + newSlot.addKW(bid.params.keywords[i]); + } + } + if (bid.params.inventory) { + for (var p in bid.params.inventory) { + if (bid.params.inventory.hasOwnProperty(p)) { + newSlot.addFPI(p,bid.params.inventory[p]); + } + } + } + if (bid.params.visitor) { + for (var p in bid.params.visitor) { + if (bid.params.visitor.hasOwnProperty(p)) { + newSlot.addFPV(p,bid.params.visitor[p]); + } + } + } + }); + } + + /** + * Handle the bids received (from rubicon) + */ + function _bidsReady() { + // NOTE: we don't really need to do anything, + // because right now we're shimming XMLHttpRequest.open, + // but in the future we'll get data from rubicontag here + utils.logMessage('Rubicon Project bidding complete: ' + ((new Date).getTime() - _bidStart)); + + utils._each(rubicontag.getAllSlots(), function (slot) { + _addBid(slot, slot.getRawResponses()); + }); + } + + + /** + * Request the specified bids from + * Rubicon + * @param {Object} params the bidder-level params (from prebid) + * @param {Array} params.bids the bids requested + */ + function _callBids(params) { + + // start the timer; want to measure from + // even just loading the SDK + _bidStart = (new Date).getTime(); + + _mapSizes(params.bids); + + utils._each(params.bids, function (bid, index) { + // on the first bid, set up the SDK + // the config will be set on each bid + if (index === 0) { + _initSDK(bid.params); + } + + _defineSlot(bid); + }); + + _rready(function () { + window.rubicontag.run(_bidsReady); + }); + } + + return { + /** + * @public callBids + * the interface to Prebid + */ + callBids: _callBids + }; + }; + + module.exports = RubiconAdapter; + + +/***/ }, +/* 3 */ +/***/ function(module, exports, __webpack_require__) { + + var CONSTANTS = __webpack_require__(4); + var objectType_function = 'function'; + var objectType_undefined = 'undefined'; + var objectType_object = 'object'; + var objectType_string = 'string'; + var objectType_number = 'number'; + + var _loggingChecked = false; + + var _lgPriceCap = 5.00; + var _mgPriceCap = 20.00; + var _hgPriceCap = 20.00; + + var t_Arr = 'Array', + t_Str = 'String', + t_Fn = 'Function', + toString = Object.prototype.toString, + hasOwnProperty = Object.prototype.hasOwnProperty, + slice = Array.prototype.slice; + + /* + * Substitues into a string from a given map using the token + * Usage + * var str = 'text %%REPLACE%% this text with %%SOMETHING%%'; + * var map = {}; + * map['replace'] = 'it was subbed'; + * map['something'] = 'something else'; + * console.log(replaceTokenInString(str, map, '%%')); => "text it was subbed this text with something else" + */ + exports.replaceTokenInString = function(str, map, token) { + this._each(map, function (value, key) { + value = (value === undefined) ? '' : value; + + var keyString = token + key.toUpperCase() + token, + re = new RegExp(keyString, 'g'); + + str = str.replace(re, value); + }); + return str; + }; + + /* utility method to get incremental integer starting from 1 */ + var getIncrementalInteger = (function() { + var count = 0; + return function() { + count++; + return count; + }; + })(); + + function _getUniqueIdentifierStr() { + return getIncrementalInteger() + Math.random().toString(16).substr(2); + } + + //generate a random string (to be used as a dynamic JSONP callback) + exports.getUniqueIdentifierStr = _getUniqueIdentifierStr; + + exports.getBidIdParamater = function(key, paramsObj) { + if (paramsObj && paramsObj[key]) { + return paramsObj[key]; + } + return ''; + }; + + exports.tryAppendQueryString = function(existingUrl, key, value) { + if (value) { + return existingUrl += key + '=' + encodeURIComponent(value) + '&'; + } + return existingUrl; + }; + + + //parse a query string object passed in bid params + //bid params should be an object such as {key: "value", key1 : "value1"} + exports.parseQueryStringParameters = function(queryObj) { + var result = ""; + for (var k in queryObj){ + if (queryObj.hasOwnProperty(k)) + result += k + "=" + encodeURIComponent(queryObj[k]) + "&"; + } + return result; + }; + + + //transform an AdServer targeting bids into a query string to send to the adserver + //bid params should be an object such as {key: "value", key1 : "value1"} + exports.transformAdServerTargetingObj = function(adServerTargeting) { + var result = ""; + if (!adServerTargeting) + return ""; + for (var k in adServerTargeting) + if (adServerTargeting.hasOwnProperty(k)) + result += k + "=" + encodeURIComponent(adServerTargeting[k]) + "&"; + return result; + }; + + //Copy all of the properties in the source objects over to the target object + //return the target object. + exports.extend = function(target, source){ + target = target || {}; + + this._each(source,function(value,prop){ + if (typeof source[prop] === objectType_object) { + target[prop] = this.extend(target[prop], source[prop]); + } else { + target[prop] = source[prop]; + } + }); + return target; + }; + + /** + * Parse a GPT-Style general size Array like `[[300, 250]]` or `"300x250,970x90"` into an array of sizes `["300x250"]` or '['300x250', '970x90']' + * @param {array[array|number]} sizeObj Input array or double array [300,250] or [[300,250], [728,90]] + * @return {array[string]} Array of strings like `["300x250"]` or `["300x250", "728x90"]` + */ + exports.parseSizesInput = function(sizeObj) { + var parsedSizes = []; + + //if a string for now we can assume it is a single size, like "300x250" + if (typeof sizeObj === objectType_string) { + //multiple sizes will be comma-separated + var sizes = sizeObj.split(','); + //regular expression to match strigns like 300x250 + //start of line, at least 1 number, an "x" , then at least 1 number, and the then end of the line + var sizeRegex = /^(\d)+x(\d)+$/i; + if (sizes) { + for (var curSizePos in sizes) { + if (hasOwn(sizes, curSizePos) && sizes[curSizePos].match(sizeRegex)) { + parsedSizes.push(sizes[curSizePos]); + } + } + } + } else if (typeof sizeObj === objectType_object) { + var sizeArrayLength = sizeObj.length; + //don't process empty array + if (sizeArrayLength > 0) { + //if we are a 2 item array of 2 numbers, we must be a SingleSize array + if (sizeArrayLength === 2 && typeof sizeObj[0] === objectType_number && typeof sizeObj[1] === objectType_number) { + parsedSizes.push(this.parseGPTSingleSizeArray(sizeObj)); + } else { + //otherwise, we must be a MultiSize array + for (var i = 0; i < sizeArrayLength; i++) { + parsedSizes.push(this.parseGPTSingleSizeArray(sizeObj[i])); + } + + } + } + } + + return parsedSizes; + + }; + + //parse a GPT style sigle size array, (i.e [300,250]) + //into an AppNexus style string, (i.e. 300x250) + exports.parseGPTSingleSizeArray = function(singleSize) { + //if we aren't exactly 2 items in this array, it is invalid + if (this.isArray(singleSize) && singleSize.length === 2 && (!isNaN(singleSize[0]) && !isNaN(singleSize[1]))) { + return singleSize[0] + 'x' + singleSize[1]; + } + }; + + exports.getTopWindowUrl = function() { + try { + return window.top.location.href; + } catch (e) { + return window.location.href; + } + }; + + exports.logMessage = function(msg) { + if (debugTurnedOn() && hasConsoleLogger()) { + console.log('MESSAGE: ' + msg); + } + }; + + function hasConsoleLogger() { + return (window.console && window.console.log); + } + exports.hasConsoleLogger = hasConsoleLogger; + + var errLogFn = (function (hasLogger) { + if (!hasLogger) return ''; + return window.console.error ? 'error' : 'log'; + }(hasConsoleLogger())); + + var debugTurnedOn = function() { + if (pbjs.logging === false && _loggingChecked === false) { + pbjs.logging = getParameterByName(CONSTANTS.DEBUG_MODE).toUpperCase() === 'TRUE'; + _loggingChecked = true; + } + + if (pbjs.logging) { + return true; + } + return false; + + }; + exports.debugTurnedOn = debugTurnedOn; + + exports.logError = function(msg, code, exception) { + var errCode = code || 'ERROR'; + if (debugTurnedOn() && hasConsoleLogger()) { + console[errLogFn].call(console, errCode + ': ' + msg, exception || ''); + } + }; + + exports.createInvisibleIframe = function _createInvisibleIframe() { + var f = document.createElement('iframe'); + f.id = _getUniqueIdentifierStr(); + f.height = 0; + f.width = 0; + f.border = '0px'; + f.hspace = '0'; + f.vspace = '0'; + f.marginWidth = '0'; + f.marginHeight = '0'; + f.style.border = '0'; + f.scrolling = 'no'; + f.frameBorder = '0'; + f.src = 'about:self'; + f.style = 'display:none'; + return f; + }; + + /* + * Check if a given paramater name exists in query string + * and if it does return the value + */ + var getParameterByName = function(name) { + var regexS = '[\\?&]' + name + '=([^&#]*)', + regex = new RegExp(regexS), + results = regex.exec(window.location.search); + if (results === null) { + return ''; + } + return decodeURIComponent(results[1].replace(/\+/g, ' ')); + }; + + exports.getPriceBucketString = function(cpm) { + var low = '', + med = '', + high = '', + cpmFloat = 0, + returnObj = { + low: low, + med: med, + high: high + }; + try { + cpmFloat = parseFloat(cpm); + if (cpmFloat) { + //round to closet .5 + if (cpmFloat > _lgPriceCap) { + returnObj.low = _lgPriceCap.toFixed(2); + } else { + returnObj.low = (Math.floor(cpm * 2) / 2).toFixed(2); + } + + //round to closet .1 + if (cpmFloat > _mgPriceCap) { + returnObj.med = _mgPriceCap.toFixed(2); + } else { + returnObj.med = (Math.floor(cpm * 10) / 10).toFixed(2); + } + + //round to closet .01 + if (cpmFloat > _hgPriceCap) { + returnObj.high = _hgPriceCap.toFixed(2); + } else { + returnObj.high = (Math.floor(cpm * 100) / 100).toFixed(2); + } + } + } catch (e) { + this.logError('Exception parsing CPM :' + e.message); + } + return returnObj; + + }; + + /** + * This function validates paramaters. + * @param {object[string]} paramObj [description] + * @param {string[]} requiredParamsArr [description] + * @return {bool} Bool if paramaters are valid + */ + exports.hasValidBidRequest = function(paramObj, requiredParamsArr, adapter){ + + for(var i = 0; i < requiredParamsArr.length; i++){ + var found = false; + + this._each(paramObj, function (value, key) { + if (key === requiredParamsArr[i]) { + found = true; + } + }); + + if(!found){ + this.logError('Params are missing for bid request. One of these required paramaters are missing: ' + requiredParamsArr, adapter); + return false; + } + } + + return true; + }; + + // Handle addEventListener gracefully in older browsers + exports.addEventHandler = function(element, event, func) { + if (element.addEventListener) { + element.addEventListener(event, func, true); + } else if (element.attachEvent) { + element.attachEvent('on' + event, func); + } + }; + /** + * Return if the object is of the + * given type. + * @param {*} object to test + * @param {String} _t type string (e.g., Array) + * @return {Boolean} if object is of type _t + */ + exports.isA = function(object, _t) { + return toString.call(object) === '[object ' + _t + ']'; + }; + + exports.isFn = function (object) { + return this.isA(object, t_Fn); + }; + + exports.isStr = function (object) { + return this.isA(object, t_Str); + }; + + exports.isArray = function (object) { + return this.isA(object, t_Arr); + }; + + /** + * Return if the object is "empty"; + * this includes falsey, no keys, or no items at indices + * @param {*} object object to test + * @return {Boolean} if object is empty + */ + exports.isEmpty = function(object) { + if (!object) return true; + if (this.isArray(object) || this.isStr(object)) return !(object.length > 0); + for (var k in object) { + if (hasOwnProperty.call(object, k)) return false; + } + return true; + }; + + /** + * Iterate object with the function + * falls back to es5 `forEach` + * @param {Array|Object} object + * @param {Function(value, key, object)} fn + */ + exports._each = function(object, fn) { + if (this.isEmpty(object)) return; + if (this.isFn(object.forEach)) return object.forEach(fn, this); + + var k = 0, + l = object.length; + + if (l > 0) { + for (; k < l; k++) fn(object[k], k, object); + } else { + for (k in object) { + if (hasOwnProperty.call(object, k)) fn.call(this, object[k], k); + } + } + }; + + exports.contains = function(a, obj) { + if(this.isEmpty(a)){ + return false; + } + if (this.isFn(a.indexOf)) { + return a.indexOf(obj) !== -1; + } + var i = a.length; + while (i--) { + if (a[i] === obj) { + return true; + } + } + return false; + }; + + /** + * Map an array or object into another array + * given a function + * @param {Array|Object} object + * @param {Function(value, key, object)} callback + * @return {Array} + */ + exports._map = function (object, callback) { + if (this.isEmpty(object)) return []; + if (this.isFn(object.map)) return object.map(callback); + var output = []; + this._each(object, function (value, key) { + output.push(callback(value, key, object)); + }); + return output; + }; + + var hasOwn = function(objectToCheck, propertyToCheckFor) { + if (objectToCheck.hasOwnProperty) { + return objectToCheck.hasOwnProperty(propertyToCheckFor); + } else { + return (typeof objectToCheck[propertyToCheckFor] !== UNDEFINED) && (objectToCheck.constructor.prototype[propertyToCheckFor] !== objectToCheck[propertyToCheckFor]); + } + }; + +/***/ }, +/* 4 */ +/***/ function(module, exports) { + + module.exports = { + "JSON_MAPPING": { + "PL_CODE": "code", + "PL_SIZE": "sizes", + "PL_BIDS": "bids", + "BD_BIDDER": "bidder", + "BD_ID": "paramsd", + "BD_PL_ID": "placementId", + "ADSERVER_TARGETING": "adserverTargeting", + "BD_SETTING_STANDARD": "standard" + }, + "DEBUG_MODE": "pbjs_debug", + "STATUS": { + "GOOD": "good", + "TIMEOUT": "timed out" + }, + "CB": { + "TYPE": { + "ALL_BIDS_BACK": "allRequestedBidsBack", + "AD_UNIT_BIDS_BACK": "adUnitBidsBack" + } + }, + "objectType_function": "function", + "objectType_undefined": "undefined", + "objectType_object": "object", + "objectType_string": "string", + "objectType_number": "number", + "EVENTS": { + "BID_ADJUSTMENT": "bidAdjustment", + "BID_TIMEOUT": "bidTimeout", + "BID_REQUESTED": "bidRequested", + "BID_RESPONSE": "bidResponse", + "BID_WON": "bidWon" + } + }; + +/***/ }, +/* 5 */ +/***/ function(module, exports, __webpack_require__) { + + var CONSTANTS = __webpack_require__(4); + var utils = __webpack_require__(3); + var adaptermanager = __webpack_require__(1); + var events = __webpack_require__(6); + + var objectType_function = 'function'; + var objectType_undefined = 'undefined'; + + var externalCallbackByAdUnitArr = []; + var externalCallbackArr = []; + var externalOneTimeCallback = null; + var biddersByPlacementMap = {}; + + var pbCallbackMap = {}; + exports.pbCallbackMap = pbCallbackMap; + + var pbBidResponseByPlacement = {}; + exports.pbBidResponseByPlacement = pbBidResponseByPlacement; + + //this is used to look up the bid by bid ID later + var _adResponsesByBidderId = {}; + exports._adResponsesByBidderId = _adResponsesByBidderId; + + var bidResponseReceivedCount = {}; + exports.bidResponseReceivedCount = bidResponseReceivedCount; + + var expectedBidsCount = {}; + + var _allBidsAvailable = false; + + var _callbackExecuted = false; + + var defaultBidderSettingsMap = {}; + var bidderStartTimes = {}; + + exports.getPlacementIdByCBIdentifer = function(id) { + return pbCallbackMap[id]; + }; + + + exports.getBidResponseByAdUnit = function(adUnitCode) { + return pbBidResponseByPlacement; + + }; + + + exports.clearAllBidResponses = function(adUnitCode) { + _allBidsAvailable = false; + _callbackExecuted = false; + + //init bid response received count + initbidResponseReceivedCount(); + //init expected bids count + initExpectedBidsCount(); + //clear the callback handler flag + externalCallbackArr.called = false; + + for (var prop in this.pbBidResponseByPlacement) { + delete this.pbBidResponseByPlacement[prop]; + } + }; + + /** + * Returns a list of bidders that we haven't received a response yet + * @return {array} [description] + */ + exports.getTimedOutBidders = function(){ + var bidderArr = []; + utils._each(bidResponseReceivedCount,function(count,bidderCode){ + if(count === 0){ + bidderArr.push(bidderCode); + } + }); + + return bidderArr; + }; + + function initbidResponseReceivedCount(){ + + bidResponseReceivedCount = {}; + + for(var i=0; i 0 ? '&' : '?'; + //add a cachebuster so we don't end up dropping any impressions + trackingPixel = pixelUrl + delimiter + 'rnd=' + Math.floor(Math.random() * 1E7); + (new Image()).src = trackingPixel; + return trackingPixel; + }; + + +/***/ }, +/* 9 */ +/***/ function(module, exports, __webpack_require__) { + + var CONSTANTS = __webpack_require__(4); + var utils = __webpack_require__(3); + var adloader = __webpack_require__(8); + var bidmanager = __webpack_require__(5); + var bidfactory = __webpack_require__(7); + var Adapter = __webpack_require__(10); + + var AppNexusAdapter = function AppNexusAdapter() { + var baseAdapter = Adapter.createNew('appnexus'); + var isCalled = false; + + baseAdapter.callBids = function(params){ + var bidCode = baseAdapter.getBidderCode(); + + var anArr = params.bids; + var bidsCount = anArr.length; + + //set expected bids count for callback execution + bidmanager.setExpectedBidsCount(bidCode,bidsCount); + + for (var i = 0; i < bidsCount; i++) { + var bidReqeust = anArr[i]; + var callbackId = utils.getUniqueIdentifierStr(); + adloader.loadScript(buildJPTCall(bidReqeust, callbackId)); + //store a reference to the bidRequest from the callback id + bidmanager.pbCallbackMap[callbackId] = bidReqeust; + } + }; + + + function buildJPTCall(bid, callbackId) { + + //determine tag params + var placementId = utils.getBidIdParamater('placementId', bid.params); + //memberId will be deprecated, use member instead + var memberId = utils.getBidIdParamater('memberId', bid.params); + var member = utils.getBidIdParamater('member', bid.params); + var inventoryCode = utils.getBidIdParamater('invCode', bid.params); + var query = utils.getBidIdParamater('query', bid.params); + var referrer = utils.getBidIdParamater('referrer', bid.params); + var altReferrer = utils.getBidIdParamater('alt_referrer', bid.params); + + //build our base tag, based on if we are http or https + + var jptCall = 'http' + ('https:' === document.location.protocol ? 's://secure.adnxs.com/jpt?' : '://ib.adnxs.com/jpt?'); + + jptCall = utils.tryAppendQueryString(jptCall, 'callback', 'pbjs.handleAnCB'); + jptCall = utils.tryAppendQueryString(jptCall, 'callback_uid', callbackId); + jptCall = utils.tryAppendQueryString(jptCall, 'psa', '0'); + jptCall = utils.tryAppendQueryString(jptCall, 'id', placementId); + if(member){ + jptCall = utils.tryAppendQueryString(jptCall, 'member_id', member); + }else if(memberId){ + jptCall = utils.tryAppendQueryString(jptCall, 'member_id', memberId); + utils.logMessage('appnexus.callBids: "memberId" will be deprecated soon. Please use "member" instead'); + } + jptCall = utils.tryAppendQueryString(jptCall, 'code', inventoryCode); + jptCall = utils.tryAppendQueryString(jptCall, 'code', inventoryCode); + + + + //sizes takes a bit more logic + var sizeQueryString = ''; + var parsedSizes = utils.parseSizesInput(bid.sizes); + + //combine string into proper querystring for impbus + var 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 (var 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 + '&'; + } + + //this will be deprecated soon + var targetingParams = utils.parseQueryStringParameters(query); + + if (targetingParams) { + //don't append a & here, we have already done it in parseQueryStringParameters + jptCall += targetingParams; + } + + //append custom attributes: + var paramsCopy = utils.extend({}, bid.params); + //delete attributes already used + delete paramsCopy.placementId; + delete paramsCopy.memberId; + delete paramsCopy.invCode; + delete paramsCopy.query; + delete paramsCopy.referrer; + delete paramsCopy.alt_referrer; + delete paramsCopy.member; + + //get the reminder + var 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); + } + + // @if NODE_ENV='debug' + utils.logMessage('jpt request built: ' + jptCall); + // @endif + + //append a timer here to track latency + bid.startTime = new Date().getTime(); + + return jptCall; + + } + + //expose the callback to the global object: + pbjs.handleAnCB = function(jptResponseObj) { + + var bidCode; + + if (jptResponseObj && jptResponseObj.callback_uid) { + + var error; + var responseCPM; + var id = jptResponseObj.callback_uid, + placementCode = '', + //retrieve bid object by callback ID + bidObj = bidmanager.getPlacementIdByCBIdentifer(id); + if (bidObj) { + + bidCode = bidObj.bidder; + + placementCode = bidObj.placementCode; + //set the status + bidObj.status = CONSTANTS.STATUS.GOOD; + } + + // @if NODE_ENV='debug' + utils.logMessage('JSONP callback function called for ad ID: ' + id); + // @endif + var bid = []; + if (jptResponseObj.result && jptResponseObj.result.cpm && jptResponseObj.result.cpm !== 0) { + responseCPM = parseInt(jptResponseObj.result.cpm, 10); + + //CPM response from /jpt is dollar/cent multiplied by 10000 + //in order to avoid using floats + //switch CPM to "dollar/cent" + responseCPM = responseCPM / 10000; + var responseAd = jptResponseObj.result.ad; + //store bid response + //bid status is good (indicating 1) + var adId = jptResponseObj.result.creative_id; + bid = bidfactory.createBid(1); + 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 = jptResponseObj.result.deal_id; + + bidmanager.addBidResponse(placementCode, bid); + + + } else { + //no response data + // @if NODE_ENV='debug' + utils.logMessage('No prebid response from AppNexus for placement code ' + placementCode); + // @endif + //indicate that there is no bid for this placement + bid = bidfactory.createBid(2); + bid.bidderCode = bidCode; + bidmanager.addBidResponse(placementCode, bid); + } + + + + } else { + //no response data + // @if NODE_ENV='debug' + utils.logMessage('No prebid response for placement %%PLACEMENT%%'); + // @endif + + } + + }; + + return { + callBids: baseAdapter.callBids, + setBidderCode: baseAdapter.setBidderCode, + createNew: exports.createNew, + buildJPTCall : buildJPTCall + }; + }; + + exports.createNew = function(){ + return new AppNexusAdapter(); + }; + // module.exports = AppNexusAdapter; + +/***/ }, +/* 10 */ +/***/ function(module, exports) { + + function Adapter(code){ + var bidderCode = code; + + function setBidderCode(code){ + bidderCode = code; + } + + function getBidderCode(){ + return bidderCode; + } + + function callBids(){ + } + + return { + callBids: callBids, + setBidderCode: setBidderCode, + getBidderCode: getBidderCode + }; + } + + exports.createNew = function(bidderCode){ + return new Adapter(bidderCode); + }; + +/***/ }, +/* 11 */ +/***/ function(module, exports, __webpack_require__) { + + var utils = __webpack_require__(3), + bidfactory = __webpack_require__(7), + bidmanager = __webpack_require__(5), + adloader = __webpack_require__(8); + + var AolAdapter = function AolAdapter() { + + // constants + var ADTECH_PLACEMENT_RXP = /\W/g, + ADTECH_URI = (window.location.protocol) + '//aka-cdn.adtechus.com/dt/common/DAC.js', + ADTECH_BIDDER_NAME = 'aol', + ADTECH_PUBAPI_CONFIG = { + pixelsDivId: 'pixelsDiv', + defaultKey: 'aolBid', + roundingConfig: [{ + from: 0, + to: 999, + roundFunction: 'tenCentsRound' + }, { + from: 1000, + to: -1, + roundValue: 1000 + }], + pubApiOK: _addBid, + pubApiER: _addErrorBid + }; + + var bids, + bidsMap = {}, + d = window.document, + h = d.getElementsByTagName('HEAD')[0], + aliasCount = 0, + dummyUnitIdCount = 0; + + /** + * @private Given a placementCode slot path/div id + * for a unit, return a unique alias + * @param {String} placementCode + * @return {String} alias + */ + function _generateAlias(placementCode) { + return (placementCode || 'alias').replace(ADTECH_PLACEMENT_RXP, '') + (++aliasCount); + } + + /** + * @private create a div that we'll use as the + * location for the AOL unit; AOL will document.write + * if the div is not present in the document. + * @param {String} id to identify the div + * @return {String} the id used with the div + */ + function _dummyUnit(id) { + var div = d.createElement('DIV'); + + if (!id || !id.length) { + id = 'ad-placeholder-' + (++dummyUnitIdCount); + } + + div.id = id + '-head-unit'; + h.appendChild(div); + return div.id; + } + + /** + * @private Add a succesful bid response for aol + * @param {ADTECHResponse} response the response for the bid + * @param {ADTECHContext} context the context passed from aol + */ + function _addBid(response, context) { + var bid = bidsMap[context.placement], + cpm; + + if (!bid) { + utils.logError('mismatched bid: ' + context.placement, ADTECH_BIDDER_NAME, context); + return; + } + + cpm = response.getCPM(); + if (cpm === null || isNaN(cpm)) { + return _addErrorBid(response, context); + } + + var bidResponse = bidfactory.createBid(1); + bidResponse.bidderCode = ADTECH_BIDDER_NAME; + bidResponse.ad = response.getCreative() + response.getPixels(); + bidResponse.cpm = cpm; + bidResponse.width = response.getAdWidth(); + bidResponse.height = response.getAdHeight(); + bidResponse.creativeId = response.getCreativeId(); + + // add it to the bid manager + bidmanager.addBidResponse(bid.placementCode, bidResponse); + } + + /** + * @private Add an error bid response for aol + * @param {ADTECHResponse} response the response for the bid + * @param {ADTECHContext} context the context passed from aol + */ + function _addErrorBid(response, context) { + var bid = bidsMap[context.alias || context.placement]; + + if (!bid) { + utils.logError('mismatched bid: ' + context.placement, ADTECH_BIDDER_NAME, context); + return; + } + + var bidResponse = bidfactory.createBid(2); + bidResponse.bidderCode = ADTECH_BIDDER_NAME; + bidResponse.reason = response.getNbr(); + bidResponse.raw = response.getResponse(); + bidmanager.addBidResponse(bid.placementCode, bidResponse); + } + + + /** + * @private map a prebid bidrequest to an ADTECH/aol bid request + * @param {Bid} bid the bid request + * @return {Object} the bid request, formatted for the ADTECH/DAC api + */ + function _mapUnit(bid) { + // save the bid + bidsMap[bid.params.placement] = bid; + + return { + adContainerId: _dummyUnit(bid.params.adContainerId), + server: bid.params.server, // By default, DAC.js will use the US region endpoint (adserver.adtechus.com) + sizeid: bid.params.sizeId || 0, + pageid: bid.params.pageId, + secure: false, + serviceType: 'pubapi', + performScreenDetection: false, + alias: bid.params.alias || _generateAlias(bid.placementCode), + network: bid.params.network, + placement: parseInt(bid.params.placement), + gpt: { + adUnitPath: bid.params.adUnitPath || bid.placementCode, + size: bid.params.size || (bid.sizes || [])[0] + }, + params: { + cors: 'yes', + cmd: 'bid' + }, + pubApiConfig: ADTECH_PUBAPI_CONFIG, + placementCode: bid.placementCode + }; + } + + /** + * @private once ADTECH is loaded, request bids by + * calling ADTECH.loadAd + */ + function _reqBids() { + if (!window.ADTECH) { + utils.logError('window.ADTECH is not present!', ADTECH_BIDDER_NAME); + return; + } + + // get the bids + utils._each(bids, function(bid) { + var bidreq = _mapUnit(bid); + window.ADTECH.loadAd(bidreq); + }); + } + + /** + * @public call the bids + * this requests the specified bids + * from aol marketplace + * @param {Object} params + * @param {Array} params.bids the bids to be requested + */ + function _callBids(params) { + bids = params.bids; + if (!bids || !bids.length) return; + adloader.loadScript(ADTECH_URI, _reqBids); + } + + return { + callBids: _callBids + }; + }; + + module.exports = AolAdapter; + +/***/ }, +/* 12 */ +/***/ function(module, exports, __webpack_require__) { + + var CONSTANTS = __webpack_require__(4); + var utils = __webpack_require__(3); + var bidfactory = __webpack_require__(7); + var bidmanager = __webpack_require__(5); + var adloader = __webpack_require__(8); + + /** + * 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; + } + if (bid.params.refererURL) { + opts.refererURL = bid.params.refererURL; + } + if (bid.params.jstag_url) { + scriptUrl = bid.params.jstag_url; + } + if (bid.params.pgid) { + opts.pgid = bid.params.pgid; + } + } + _requestBids(); + } + + function _requestBids() { + + if (scriptUrl) { + adloader.loadScript(scriptUrl, function() { + var i; + var POX = OX(); + + POX.setPageURL(opts.pageURL); + POX.setRefererURL(opts.refererURL); + POX.addPage(opts.pgid); + + // Add each ad unit ID + for (i = 0; i < bids.length; i++) { + POX.addAdUnit(bids[i].params.unit); + } + + 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'); + // 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(); + }); + } + } + + return { + callBids: _callBids + }; + }; + + module.exports = OpenxAdapter; + + +/***/ }, +/* 13 */ +/***/ function(module, exports, __webpack_require__) { + + var CONSTANTS = __webpack_require__(4); + var utils = __webpack_require__(3); + var bidfactory = __webpack_require__(7); + var bidmanager = __webpack_require__(5); + var adloader = __webpack_require__(8); + + /** + * Adapter for requesting bids from Pubmatic. + * + * @returns {{callBids: _callBids}} + * @constructor + */ + var PubmaticAdapter = function PubmaticAdapter() { + + var bids; + var _pm_pub_id; + var _pm_optimize_adslots = []; + + function _callBids(params) { + bids = params.bids; + for (var i = 0; i < bids.length; i++) { + var bid = bids[i]; + bidmanager.pbCallbackMap['' + bid.params.adSlot] = bid; + _pm_pub_id = _pm_pub_id || bid.params.publisherId; + _pm_optimize_adslots.push(bid.params.adSlot); + } + + // Load pubmatic script in an iframe, because they call document.write + _getBids(); + } + + function _getBids() { + + // required variables for pubmatic pre-bid call + window.pm_pub_id = _pm_pub_id; + window.pm_optimize_adslots = _pm_optimize_adslots; + + //create the iframe + var iframe = utils.createInvisibleIframe(); + var elToAppend = document.getElementsByTagName('head')[0]; + //insert the iframe into document + elToAppend.insertBefore(iframe, elToAppend.firstChild); + //todo make this more browser friendly + var iframeDoc = iframe.contentWindow.document; + iframeDoc.write(_createRequestContent()); + iframeDoc.close(); + } + + function _createRequestContent() { + var content = 'inDapIF=true;'; + content += ''; + content += ''; + content += '' + + 'window.pm_pub_id = "%%PM_PUB_ID%%";' + + 'window.pm_optimize_adslots = [%%PM_OPTIMIZE_ADSLOTS%%];'; + content += ''; + + var map = {}; + map['PM_PUB_ID'] = _pm_pub_id; + map['PM_OPTIMIZE_ADSLOTS'] = _pm_optimize_adslots.map(function(adSlot) { + return "'" + adSlot + "'"; + }).join(','); + + content += ''; + content += ''; + content += 'window.parent.pbjs.handlePubmaticCallback({progKeyValueMap: progKeyValueMap, bidDetailsMap: bidDetailsMap})'; + content += ''; + content += ''; + content = utils.replaceTokenInString(content, map, '%%'); + + return content; + } + + pbjs.handlePubmaticCallback = function(response) { + var i; + var adUnit; + var adUnitInfo; + var bid; + var bidResponseMap = (response && response.bidDetailsMap) || {}; + var bidInfoMap = (response && response.progKeyValueMap) || {}; + var dimensions; + + for (i = 0; i < bids.length; i++) { + var adResponse; + bid = bids[i].params; + + adUnit = bidResponseMap[bid.adSlot] || {}; + + // adUnitInfo example: bidstatus=0;bid=0.0000;bidid=39620189@320x50;wdeal= + adUnitInfo = (bidInfoMap[bid.adSlot] || '').split(';').reduce(function(result, pair) { + var parts = pair.split('='); + result[parts[0]] = parts[1]; + return result; + }, {}); + + if (adUnitInfo.bidstatus === '1') { + dimensions = adUnitInfo.bidid.split('@')[1].split('x'); + adResponse = bidfactory.createBid(1); + adResponse.bidderCode = 'pubmatic'; + adResponse.adSlot = bid.adSlot; + adResponse.cpm = Number(adUnitInfo.bid); + adResponse.ad = unescape(adUnit.creative_tag); + adResponse.adUrl = unescape(adUnit.tracking_url); + adResponse.width = dimensions[0]; + adResponse.height = dimensions[1]; + adResponse.dealId = adUnitInfo.wdeal; + + bidmanager.addBidResponse(bids[i].placementCode, adResponse); + } else { + // Indicate an ad was not returned + adResponse = bidfactory.createBid(2); + adResponse.bidderCode = 'pubmatic'; + bidmanager.addBidResponse(bids[i].placementCode, adResponse); + } + } + }; + + return { + callBids: _callBids + }; + + }; + + module.exports = PubmaticAdapter; + +/***/ }, +/* 14 */ +/***/ function(module, exports, __webpack_require__) { + + /** + * @overview Yieldbot sponsored Prebid.js adapter. + * @author elljoh + */ + var adloader = __webpack_require__(8); + var bidfactory = __webpack_require__(7); + var bidmanager = __webpack_require__(5); + var utils = __webpack_require__(3); + + /** + * Adapter for requesting bids from Yieldbot. + * + * @returns {Object} Object containing implementation for invocation in {@link module:adaptermanger.callBids} + * @class + */ + var YieldbotAdapter = function YieldbotAdapter() { + + window.ybotq = window.ybotq || []; + + var ybotlib = { + BID_STATUS: { + PENDING: 0, + AVAILABLE: 1, + EMPTY: 2 + }, + definedSlots: [], + pageLevelOption: false, + /** + * Builds the Yieldbot creative tag. + * + * @param {String} slot - The slot name to bid for + * @param {String} size - The dimenstions of the slot + * @private + */ + buildCreative: function(slot, size) { + return '' + + ''; + }, + /** + * Bid response builder. + * + * @param {Object} slotCriteria - Yieldbot bid criteria + * @private + */ + buildBid: function(slotCriteria) { + var bid = {}; + + if (slotCriteria && slotCriteria.ybot_ad && slotCriteria.ybot_ad !== 'n') { + + bid = bidfactory.createBid(ybotlib.BID_STATUS.AVAILABLE); + + bid.cpm = parseInt(slotCriteria.ybot_cpm) / 100.0 || 0; // Yieldbot CPM bids are in cents + + var szArr = slotCriteria.ybot_size ? slotCriteria.ybot_size.split('x') : [0,0], + slot = slotCriteria.ybot_slot || '', + sizeStr = slotCriteria.ybot_size || ''; // Creative template needs the dimensions string + + bid.width = szArr[0] || 0; + bid.height = szArr[1] || 0; + + bid.ad = ybotlib.buildCreative(slot, sizeStr); + + // Add Yieldbot parameters to allow publisher bidderSettings.yieldbot specific targeting + for (var k in slotCriteria) { + bid[k] = slotCriteria[k]; + } + + } else { + bid = bidfactory.createBid(ybotlib.BID_STATUS.EMPTY); + } + + bid.bidderCode = 'yieldbot'; + return bid; + }, + /** + * Yieldbot implementation of {@link module:adaptermanger.callBids} + * @param {Object} params - Adapter bid configuration object + * @private + */ + callBids: function(params) { + + var bids = params.bids || [], + ybotq = window.ybotq || []; + + ybotlib.pageLevelOption = false; + + ybotq.push(function () { + var yieldbot = window.yieldbot; + + utils._each(bids, function(v) { + var bid = v, + psn = bid.params && bid.params.psn || 'ERROR_DEFINE_YB_PSN', + slot = bid.params && bid.params.slot || 'ERROR_DEFINE_YB_SLOT'; + + yieldbot.pub(psn); + yieldbot.defineSlot(slot, {sizes: bid.sizes || []}); + + var cbId = utils.getUniqueIdentifierStr(); + bidmanager.pbCallbackMap[cbId] = bid; + ybotlib.definedSlots.push(cbId); + }); + + yieldbot.enableAsync(); + yieldbot.go(); + }); + + ybotq.push(function () { + ybotlib.handleUpdateState(); + }); + + adloader.loadScript('//cdn.yldbt.com/js/yieldbot.intent.js'); + }, + /** + * Yieldbot bid request callback handler. + * + * @see {@link YieldbotAdapter~_callBids} + * @private + */ + handleUpdateState: function() { + var yieldbot = window.yieldbot; + + utils._each(ybotlib.definedSlots, function(v) { + var slot, + criteria, + placementCode, + adapterConfig; + + adapterConfig = bidmanager.getPlacementIdByCBIdentifer(v) || {}; + slot = adapterConfig.params.slot || ''; + criteria = yieldbot.getSlotCriteria(slot); + + placementCode = adapterConfig.placementCode || 'ERROR_YB_NO_PLACEMENT'; + var bid = ybotlib.buildBid(criteria); + + bidmanager.addBidResponse(placementCode, bid); + + }); + } + } + return { + callBids: ybotlib.callBids + }; + }; + + module.exports = YieldbotAdapter; + + +/***/ }, +/* 15 */ +/***/ function(module, exports, __webpack_require__) { + + //Factory for creating the bidderAdaptor + var CONSTANTS = __webpack_require__(4); + var utils = __webpack_require__(3); + var bidfactory = __webpack_require__(7); + var bidmanager = __webpack_require__(5); + var adloader = __webpack_require__(8); + + var ADAPTER_NAME = 'INDEXEXCHANGE'; + var ADAPTER_CODE = 'indexExchange'; + + var cygnus_index_primary_request = true; + var cygnus_index_parse_res = function() {}; + window.cygnus_index_args = {}; + + var cygnus_index_adunits = [[728,90],[120,600],[300,250],[160,600],[336,280],[234,60],[300,600],[300,50],[320,50],[970,250],[300,1050],[970,90],[180,150]]; + + var cygnus_index_start = function() { + cygnus_index_args.parseFn = cygnus_index_parse_res; + var escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; + var meta = { + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"': '\\"', + '\\': '\\\\' + }; + + function escapeCharacter(character) { + var escaped = meta[character]; + if (typeof escaped === 'string') { + return escaped; + } else { + return '\\u' + ('0000' + character.charCodeAt(0).toString(16)).slice(-4); + } + } + + function quote(string) { + escapable.lastIndex = 0; + if (escapable.test(string)) { + return string.replace(escapable, escapeCharacter); + } else { + return string; + } + } + + function OpenRTBRequest(siteID, parseFn, timeoutDelay) { + this.initialized = false; + if (typeof siteID !== "number" || siteID % 1 !== 0 || siteID < 0) { + throw "Invalid Site ID"; + } + if (typeof timeoutDelay === "number" && timeoutDelay % 1 === 0 && timeoutDelay >= 0) { + this.timeoutDelay = timeoutDelay; + } + + this.siteID = siteID; + this.impressions = []; + this._parseFnName = undefined; + if (top === self) { + this.sitePage = location.href; + this.topframe = 1; + } else { + this.sitePage = document.referrer; + this.topframe = 0; + } + if (typeof parseFn !== 'undefined') { + if (typeof parseFn === 'function') { + this._parseFnName = "cygnus_index_args.parseFn"; + } else { + throw "Invalid jsonp target function"; + } + } + if (typeof _IndexRequestData.requestCounter === 'undefined') { + _IndexRequestData.requestCounter = Math.floor(Math.random() * 256); + } else { + _IndexRequestData.requestCounter = (_IndexRequestData.requestCounter + 1) % 256; + } + this.requestID = String((new Date().getTime() % 2592000) * 256 + _IndexRequestData.requestCounter + 256); + this.initialized = true; + } + OpenRTBRequest.prototype.serialize = function() { + var json = '{"id":' + this.requestID + ',"site":{"page":"' + quote(this.sitePage) + '"'; + if (typeof document.referrer === 'string') { + json += ',"ref":"' + quote(document.referrer) + '"'; + } + json += '},"imp":['; + for (var i = 0; i < this.impressions.length; i++) { + var impObj = this.impressions[i]; + var ext = []; + json += '{"id":"' + impObj.id + '", "banner":{"w":' + impObj.w + ',"h":' + impObj.h + ',"topframe":' + String(this.topframe) + "}"; + if (typeof impObj.bidfloor === 'number') { + json += ',"bidfloor":' + impObj.bidfloor; + if (typeof impObj.bidfloorcur === 'string') { + json += ',"bidfloorcur":"' + quote(impObj.bidfloorcur) + '"'; + } + } + if (typeof impObj.slotID === 'string' && (!impObj.slotID.match(/^\s*$/))) { + ext.push('"sid":"' + quote(impObj.slotID) + '"'); + } + if (typeof impObj.siteID === 'number') { + ext.push('"siteID":' + impObj.siteID); + } + if (ext.length > 0) { + json += ',"ext": {' + ext.join() + '}'; + } + if (i + 1 == this.impressions.length) { + json += '}'; + } else { + json += '},'; + } + } + json += "]}"; + return json; + }; + OpenRTBRequest.prototype.setPageOverride = function(sitePageOverride) { + if (typeof sitePageOverride === 'string' && (!sitePageOverride.match(/^\s*$/))) { + this.sitePage = sitePageOverride; + return true; + } else { + return false; + } + }; + OpenRTBRequest.prototype.addImpression = function(width, height, bidFloor, bidFloorCurrency, slotID, siteID) { + var impObj = { + 'id': String(this.impressions.length + 1) + }; + if (typeof width !== 'number' || width <= 1) { + return null; + } + if (typeof height !== 'number' || height <= 1) { + return null; + } + if ((typeof slotID === 'string' || typeof slotID === 'number') && String(slotID).length <= 50) { + impObj.slotID = String(slotID); + } + impObj.w = width; + impObj.h = height; + if (bidFloor !== undefined && typeof bidFloor !== 'number') { + return null; + } + if (typeof bidFloor === 'number') { + if (bidFloor < 0) { + return null; + } + impObj.bidfloor = bidFloor; + if (bidFloorCurrency !== undefined && typeof bidFloorCurrency !== 'string') { + return null; + } + impObj.bidfloorcur = bidFloorCurrency; + } + if (typeof siteID !== 'undefined') { + if (typeof siteID === 'number' && siteID % 1 === 0 && siteID >= 0) { + impObj.siteID = siteID; + } else { + return null; + } + } + this.impressions.push(impObj); + return impObj.id; + }; + + OpenRTBRequest.prototype.buildRequest = function() { + if (this.impressions.length === 0 || this.initialized !== true) { + return; + } + var jsonURI = encodeURIComponent(this.serialize()); + var scriptSrc = window.location.protocol === 'https:' ? 'https://as-sec.casalemedia.com' : 'http://as.casalemedia.com'; + scriptSrc += '/headertag?v=9&x3=1&fn=cygnus_index_parse_res&s=' + this.siteID + '&r=' + jsonURI; + if (typeof this.timeoutDelay === "number" && this.timeoutDelay % 1 === 0 && this.timeoutDelay >= 0) { + scriptSrc += '&t=' + this.timeoutDelay; + } + return scriptSrc; + }; + try { + if (typeof cygnus_index_args === 'undefined' || typeof cygnus_index_args.siteID === 'undefined' || typeof cygnus_index_args.slots === 'undefined') { + return; + } + if (typeof _IndexRequestData === 'undefined') { + _IndexRequestData = {}; + _IndexRequestData.impIDToSlotID = {}; + _IndexRequestData.reqOptions = {}; + } + var req = new OpenRTBRequest(cygnus_index_args.siteID, cygnus_index_args.parseFn, cygnus_index_args.timeout); + if (cygnus_index_args.url && typeof cygnus_index_args.url === 'string') { + req.setPageOverride(cygnus_index_args.url); + } + _IndexRequestData.impIDToSlotID[req.requestID] = {}; + _IndexRequestData.reqOptions[req.requestID] = {}; + var slotDef, impID; + + for (var i = 0; i < cygnus_index_args.slots.length; i++) { + slotDef = cygnus_index_args.slots[i]; + + impID = req.addImpression(slotDef.width, slotDef.height, slotDef.bidfloor, slotDef.bidfloorcur, slotDef.id, slotDef.siteID); + if (impID) { + _IndexRequestData.impIDToSlotID[req.requestID][impID] = String(slotDef.id); + } + } + if (typeof cygnus_index_args.targetMode === 'number') { + _IndexRequestData.reqOptions[req.requestID].targetMode = cygnus_index_args.targetMode; + } + if (typeof cygnus_index_args.callback === 'function') { + _IndexRequestData.reqOptions[req.requestID].callback = cygnus_index_args.callback; + } + return req.buildRequest(); + } catch (e) {} + }; + + var IndexExchangeAdapter = function IndexExchangeAdapter() { + var slotIdMap = {}; + var requiredParams = [ + /* 0 */ + 'id', + /* 1 */ + 'siteID' + ]; + var firstAdUnitCode = ''; + + function _callBids(request) { + var bidArr = request.bids; + + if (!utils.hasValidBidRequest(bidArr[0].params, requiredParams, ADAPTER_NAME)) { + return; + } + + cygnus_index_args.slots = []; + var bidCount = 0; + + //Grab the slot level data for cygnus_index_args + for (i = 0; i < bidArr.length; i++) { + var bid = bidArr[i]; + + var width; + var height; + + outer: for (var j = 0; j < bid.sizes.length; j++) { + inner: for (var k = 0; k < cygnus_index_adunits.length; k++) { + if (bid.sizes[j][0] === cygnus_index_adunits[k][0] && + bid.sizes[j][1] === cygnus_index_adunits[k][1]) { + width = bid.sizes[j][0]; + height = bid.sizes[j][1]; + break outer; + } + } + } + + if (bid.params.timeout && typeof cygnus_index_args.timeout === 'undefined') { + cygnus_index_args.timeout = bid.params.timeout; + } + + if (bid.params.siteID && typeof cygnus_index_args.siteID === 'undefined') { + cygnus_index_args.siteID = bid.params.siteID; + } + + if (bid.params.sqps && typeof cygnus_index_args.SQPS === 'undefined') { + cygnus_index_args.slots.push({ + id:"SPQS", + width: bid.params.sqps.width, + height: bid.params.sqps.height, + siteID: bid.params.sqps.siteID || cygnus_index_args.siteID + }); + } + + if (utils.hasValidBidRequest(bid.params, requiredParams, ADAPTER_NAME)) { + firstAdUnitCode = bid.placementCode; + var slotId = bid.params[requiredParams[0]]; + slotIdMap[slotId] = bid; + + if (cygnus_index_primary_request) { + cygnus_index_args.slots.push({ + id: bid.params.id, + width: width, + height: height, + siteID: bid.params.siteID || cygnus_index_args.siteID + }); + + bidCount++; + + if (bid.params.tier2SiteID) { + cygnus_index_args.slots.push({ + id: "T1_"+bid.params.id, + width: width, + height: height, + siteID: bid.params.tier2SiteID + }); + } + if (bid.params.tier3SiteID) { + cygnus_index_args.slots.push({ + id:"T2_"+bid.params.id, + width:width, + height:height, + siteID:bid.params.tier3SiteID + }); + } + } + } + bidmanager.setExpectedBidsCount(ADAPTER_CODE, bidCount); + } + cygnus_index_primary_request = false; + + adloader.loadScript(cygnus_index_start()); + + window.cygnus_index_ready_state = function() { + try { + var indexObj = _IndexRequestData.targetIDToBid; + var lookupObj = cygnus_index_args; + + if (utils.isEmpty(indexObj)) { + var bid = bidfactory.createBid(2); + bid.bidderCode = ADAPTER_CODE; + logErrorBidResponse(); + return; + } + + utils._each(indexObj, function(adContents, cpmAndSlotId) { + utils._each(slotIdMap, function(bid, adSlotId) { + var obj = cpmAndSlotId.split('_'); + var currentId = obj[0]; + var currentCPM = obj[1]; + if (currentId === adSlotId) { + var bidObj = slotIdMap[adSlotId]; + var adUnitCode = bidObj.placementCode; + var slotObj = getSlotObj(cygnus_index_args, adSlotId); + + bid = bidfactory.createBid(1); + bid.cpm = currentCPM / 100; + bid.ad = adContents[0]; + bid.ad_id = adSlotId; + bid.bidderCode = ADAPTER_CODE; + bid.width = slotObj.width; + bid.height = slotObj.height; + bid.siteID = slotObj.siteID; + + bidmanager.addBidResponse(adUnitCode, bid); + } + }); + }); + } catch (e) { + utils.logError('Error calling index adapter', ADAPTER_NAME, e); + logErrorBidResponse(); + } + }; + } + + function getSlotObj(obj, id) { + var arr = obj.slots; + var returnObj = {}; + utils._each(arr, function(value) { + if (value.id === id) { + returnObj = value; + } + }); + return returnObj; + } + + function logErrorBidResponse() { + //no bid response + var bid = bidfactory.createBid(2); + bid.bidderCode = ADAPTER_CODE; + //log error to first add unit + bidmanager.addBidResponse(firstAdUnitCode, bid); + } + + return { + callBids: _callBids + }; + //end of Rubicon bid adaptor + }; + + module.exports = IndexExchangeAdapter; + + +/***/ }, +/* 16 */ +/***/ function(module, exports, __webpack_require__) { + + var CONSTANTS = __webpack_require__(4); + var utils = __webpack_require__(3); + var bidfactory = __webpack_require__(7); + var bidmanager = __webpack_require__(5); + var adloader = __webpack_require__(8); + + var allPlacementCodes; + + /** + * Adapter for requesting bids from Sovrn + */ + var SovrnAdapter = function SovrnAdapter() { + var sovrnUrl = 'ap.lijit.com/rtb/bid'; + + function _callBids(params) { + var sovrnBids = params.bids || []; + // De-dupe by tagid then issue single bid request for all bids + _requestBids(_getUniqueTagids(sovrnBids)); + } + + // filter bids to de-dupe them? + function _getUniqueTagids(bids) { + var key; + var map = {}; + var Tagids = []; + bids.forEach(function(bid) { + map[utils.getBidIdParamater('tagid', bid.params)] = bid; + }); + for (key in map) { + if (map.hasOwnProperty(key)) { + Tagids.push(map[key]); + } + } + return Tagids; + } + + function _requestBids(bidReqs) { + // build bid request object + var domain = window.location.host; + var page = window.location.pathname + location.search + location.hash; + + var sovrnImps = []; + allPlacementCodes = []; + //build impression array for sovrn + utils._each(bidReqs, function(bid) + { + var tagId = utils.getBidIdParamater('tagid', bid.params); + var bidFloor = utils.getBidIdParamater('bidfloor', bid.params); + var adW=0,adH=0; + + //sovrn supports only one size per tagid, so we just take the first size if there are more + //if we are a 2 item array of 2 numbers, we must be a SingleSize array + var sizeArrayLength = bid.sizes.length; + if (sizeArrayLength === 2 && typeof bid.sizes[0] === 'number' && typeof bid.sizes[1] === 'number') { + adW=bid.sizes[0]; + adH=bid.sizes[1]; + } + else + { + adW=bid.sizes[0][0]; + adH=bid.sizes[0][1]; + } + var imp = + { + id: utils.getUniqueIdentifierStr(), + banner: { + w: adW, + h: adH + }, + tagid: tagId, + bidfloor: bidFloor + }; + sovrnImps.push(imp); + bidmanager.pbCallbackMap[imp.id] = bid; + allPlacementCodes.push(bid.placementCode); + }); + + // build bid request with impressions + var sovrnBidReq = { + id: utils.getUniqueIdentifierStr(), + imp: sovrnImps, + site:{ + domain: domain, + page: page + } + }; + + var scriptUrl = '//'+sovrnUrl+'?callback=window.pbjs.sovrnResponse' + + '&br=' + encodeURIComponent(JSON.stringify(sovrnBidReq)); + adloader.loadScript(scriptUrl, null); + } + + function addBlankBidResponsesForAllPlacementsExceptThese(placementsWithBidsBack){ + utils._each(allPlacementCodes, function(placementCode) + { + if(utils.contains(placementsWithBidsBack, placementCode)) { + // A bid was returned for this placement already + } else { + // Add a no-bid response for this placement. + var bid = {}; + bid = bidfactory.createBid(2); + bid.bidderCode = 'sovrn'; + bidmanager.addBidResponse(placementCode, bid); + } + }); + } + + + //expose the callback to the global object: + pbjs.sovrnResponse = function(sovrnResponseObj) { + // valid object? + if (sovrnResponseObj && sovrnResponseObj.id) { + // valid object w/ bid responses? + if (sovrnResponseObj.seatbid && sovrnResponseObj.seatbid.length !==0 && sovrnResponseObj.seatbid[0].bid && sovrnResponseObj.seatbid[0].bid.length !== 0) { + var placementsWithBidsBack = []; + sovrnResponseObj.seatbid[0].bid.forEach(function(sovrnBid){ + + var responseCPM; + var placementCode = ''; + var id = sovrnBid.impid; + var bid = {}; + + // try to fetch the bid request we sent Sovrn + var bidObj = bidmanager.getPlacementIdByCBIdentifer(id); + if (bidObj){ + placementCode = bidObj.placementCode; + placementsWithBidsBack.push(placementCode); + bidObj.status = CONSTANTS.STATUS.GOOD; + + //place ad response on bidmanager._adResponsesByBidderId + responseCPM = parseFloat(sovrnBid.price); + + if(responseCPM !== 0) { + sovrnBid.placementCode = placementCode; + sovrnBid.size = bidObj.sizes; + var responseAd = sovrnBid.adm; + + // build impression url from response + var responseNurl = ''; + + //store bid response + //bid status is good (indicating 1) + bid = bidfactory.createBid(1); + bid.creative_id = sovrnBid.Id; + bid.bidderCode = 'sovrn'; + bid.cpm = responseCPM; + + //set ad content + impression url + // sovrn returns '; + bid.ad_id = rubiconAd.ad_id; + bid.bidderCode = 'rubicon'; + bid.sizeId = rubiconAd.size_id; + bid.width = width; + bid.height = height; + + }else{ + bid = bidfactory.createBid(2); + bid.bidderCode = 'rubicon'; + var bidObj = bidmanager.getPlacementIdByCBIdentifer(getBidId(response)); + if (bidObj) { + placementCode = bidObj.placementCode; + } + } + + } catch (e) { + utils.logError('Error parsing rubicon response bid: ' + e.message); + } + + } else { + //set bid response code to 2 = no response or error + bid = bidfactory.createBid(2); + bid.bidderCode = 'rubicon'; + var bidObj = bidmanager.getPlacementIdByCBIdentifer(getBidId(response)); + if (bidObj) { + placementCode = bidObj.placementCode; + } + + } + + //add the bid response here + bidmanager.addBidResponse(placementCode, bid); + + }; + + return { + callBids: callBids + + }; + //end of Rubicon bid adaptor + }; + + module.exports = RubiconAdapter; + +/***/ } +/******/ ]); \ No newline at end of file diff --git a/build/dist/prebid.js b/build/dist/prebid.js new file mode 100644 index 00000000000..6267c32de8b --- /dev/null +++ b/build/dist/prebid.js @@ -0,0 +1,2 @@ +!function(e){function t(i){if(r[i])return r[i].exports;var n=r[i]={exports:{},id:i,loaded:!1};return e[i].call(n.exports,n,n.exports,t),n.loaded=!0,n.exports}var r={};return t.m=e,t.c=r,t.p="",t(0)}([function(e,t,r){r(1),r(8),r(7),r(5),r(6),r(20),r(21),r(3),r(10),r(19),r(11),r(9),r(15),r(12),r(13),r(17),r(2),r(22),r(16),r(18),e.exports=r(14)},function(e,t,r){var i=r(2),n=r(9),a=r(11),d=r(12),o=r(13),s=r(14),c=r(15),u=r(16),p=r(17),l=r(18),f=r(19),g=r(5),m=r(3),h=r(4),b=r(6),v={};t.bidderRegistry=v,t.callBids=function(e){for(var t=0;t;(function (w, fe) { w.rubicontag.renderCreative(fe, "',_='"); }(window.top, (document.body || document.documentElement)));';window.rubicontag=window.rubicontag||{},window.rubicontag.cmd=window.rubicontag.cmd||[];var w=null;return{callBids:g}};e.exports=o},function(e,t,r){function i(){return v()+Math.random().toString(16).substr(2)}function n(){return window.console&&window.console.log}{var a=r(4),d="object",o="string",s="number",c=!1,u=5,p=20,l=20,f="Array",g="String",m="Function",h=Object.prototype.toString,b=Object.prototype.hasOwnProperty;Array.prototype.slice}t.replaceTokenInString=function(e,t,r){return this._each(t,function(t,i){t=void 0===t?"":t;var n=r+i.toUpperCase()+r,a=new RegExp(n,"g");e=e.replace(a,t)}),e};var v=function(){var e=0;return function(){return e++,e}}();t.getUniqueIdentifierStr=i,t.getBidIdParamater=function(e,t){return t&&t[e]?t[e]:""},t.tryAppendQueryString=function(e,t,r){return r?e+=t+"="+encodeURIComponent(r)+"&":e},t.parseQueryStringParameters=function(e){var t="";for(var r in e)e.hasOwnProperty(r)&&(t+=r+"="+encodeURIComponent(e[r])+"&");return t},t.transformAdServerTargetingObj=function(e){var t="";if(!e)return"";for(var r in e)e.hasOwnProperty(r)&&(t+=r+"="+encodeURIComponent(e[r])+"&");return t},t.extend=function(e,t){return e=e||{},this._each(t,function(r,i){e[i]=typeof t[i]===d?this.extend(e[i],t[i]):t[i]}),e},t.parseSizesInput=function(e){var t=[];if(typeof e===o){var r=e.split(","),i=/^(\d)+x(\d)+$/i;if(r)for(var n in r)B(r,n)&&r[n].match(i)&&t.push(r[n])}else if(typeof e===d){var a=e.length;if(a>0)if(2===a&&typeof e[0]===s&&typeof e[1]===s)t.push(this.parseGPTSingleSizeArray(e));else for(var c=0;a>c;c++)t.push(this.parseGPTSingleSizeArray(e[c]))}return t},t.parseGPTSingleSizeArray=function(e){return!this.isArray(e)||2!==e.length||isNaN(e[0])||isNaN(e[1])?void 0:e[0]+"x"+e[1]},t.getTopWindowUrl=function(){try{return window.top.location.href}catch(e){return window.location.href}},t.logMessage=function(e){_()&&n()&&console.log("MESSAGE: "+e)},t.hasConsoleLogger=n;var y=function(e){return e?window.console.error?"error":"log":""}(n()),_=function(){return pbjs.logging===!1&&c===!1&&(pbjs.logging="TRUE"===w(a.DEBUG_MODE).toUpperCase(),c=!0),pbjs.logging?!0:!1};t.debugTurnedOn=_,t.logError=function(e,t,r){var i=t||"ERROR";_()&&n()&&console[y].call(console,i+": "+e,r||"")},t.createInvisibleIframe=function(){var e=document.createElement("iframe");return e.id=i(),e.height=0,e.width=0,e.border="0px",e.hspace="0",e.vspace="0",e.marginWidth="0",e.marginHeight="0",e.style.border="0",e.scrolling="no",e.frameBorder="0",e.src="about:self",e.style="display:none",e};var w=function(e){var t="[\\?&]"+e+"=([^&#]*)",r=new RegExp(t),i=r.exec(window.location.search);return null===i?"":decodeURIComponent(i[1].replace(/\+/g," "))};t.getPriceBucketString=function(e){var t="",r="",i="",n=0,a={low:t,med:r,high:i};try{n=parseFloat(e),n&&(a.low=n>u?u.toFixed(2):(Math.floor(2*e)/2).toFixed(2),a.med=n>p?p.toFixed(2):(Math.floor(10*e)/10).toFixed(2),a.high=n>l?l.toFixed(2):(Math.floor(100*e)/100).toFixed(2))}catch(d){this.logError("Exception parsing CPM :"+d.message)}return a},t.hasValidBidRequest=function(e,t,r){for(var i=0;i0);for(var t in e)if(b.call(e,t))return!1;return!0},t._each=function(e,t){if(!this.isEmpty(e)){if(this.isFn(e.forEach))return e.forEach(t,this);var r=0,i=e.length;if(i>0)for(;i>r;r++)t(e[r],r,e);else for(r in e)b.call(e,r)&&t.call(this,e[r],r)}},t.contains=function(e,t){if(this.isEmpty(e))return!1;if(this.isFn(e.indexOf))return-1!==e.indexOf(t);for(var r=e.length;r--;)if(e[r]===t)return!0;return!1},t._map=function(e,t){if(this.isEmpty(e))return[];if(this.isFn(e.map))return e.map(t);var r=[];return this._each(e,function(i,n){r.push(t(i,n,e))}),r};var B=function(e,t){return e.hasOwnProperty?e.hasOwnProperty(t):typeof e[t]!==UNDEFINED&&e.constructor.prototype[t]!==e[t]}},function(e){e.exports={JSON_MAPPING:{PL_CODE:"code",PL_SIZE:"sizes",PL_BIDS:"bids",BD_BIDDER:"bidder",BD_ID:"paramsd",BD_PL_ID:"placementId",ADSERVER_TARGETING:"adserverTargeting",BD_SETTING_STANDARD:"standard"},DEBUG_MODE:"pbjs_debug",STATUS:{GOOD:"good",TIMEOUT:"timed out"},CB:{TYPE:{ALL_BIDS_BACK:"allRequestedBidsBack",AD_UNIT_BIDS_BACK:"adUnitBidsBack"}},objectType_function:"function",objectType_undefined:"undefined",objectType_object:"object",objectType_string:"string",objectType_number:"number",EVENTS:{BID_ADJUSTMENT:"bidAdjustment",BID_TIMEOUT:"bidTimeout",BID_REQUESTED:"bidRequested",BID_RESPONSE:"bidResponse",BID_WON:"bidWon"}}},function(e,t,r){function i(){S={};for(var e=0;et)&&(e=!1)}),e}function f(e){var t=e.bidderCode,r=e.cpm;if(t&&pbjs.bidderSettings&&pbjs.bidderSettings[t]&&typeof pbjs.bidderSettings[t].bidCpmAdjustment===b)try{r=pbjs.bidderSettings[t].bidCpmAdjustment.call(null,e.cpm)}catch(i){m.logError("Error during bid adjustment","bidmanager.js",i)}0!==r&&(e.cpm=r)}var g=r(4),m=r(3),h=(r(1),r(6)),b="function",v="undefined",y=[],_=[],w=null,B={},I={};t.pbCallbackMap=I;var C={};t.pbBidResponseByPlacement=C;var E={};t._adResponsesByBidderId=E;var S={};t.bidResponseReceivedCount=S;var A={},T=!1,R=!1,D={},P={};t.getPlacementIdByCBIdentifer=function(e){return I[e]},t.getBidResponseByAdUnit=function(){return C},t.clearAllBidResponses=function(){T=!1,R=!1,i(),a(),_.called=!1;for(var e in this.pbBidResponseByPlacement)delete this.pbBidResponseByPlacement[e]},t.getTimedOutBidders=function(){var e=[];return m._each(S,function(t,r){0===t&&e.push(r)}),e},t.increaseBidResponseReceivedCount=function(e){n(e)},t.setExpectedBidsCount=function(e,t){A[e]=t},t.getExpectedBidsCount=d,t.addBidResponse=function(e,t){var r={};if(t){t.requestTimestamp=P[t.bidderCode],t.responseTimestamp=(new Date).getTime(),t.timeToRespond=t.responseTimestamp-t.requestTimestamp,n(t.bidderCode),2===t.getStatusCode()&&(t.cpm=0),h.emit(g.EVENTS.BID_ADJUSTMENT,t),h.emit(g.EVENTS.BID_RESPONSE,e,t);var i=m.getPriceBucketString(t.cpm,t.height,t.width);t.pbLg=i.low,t.pbMg=i.med,t.pbHg=i.high,t.adUnitCode=e,t.bidder=t.bidderCode;var a={};t.bidderCode&&0!==t.cpm&&(a=this.getKeyValueTargetingPairs(t.bidderCode,t),t.adserverTargeting=a),t.adId&&(E[t.adId]=t),e&&C[e]?(r=C[e],r.bids.push(t),r.bidsReceivedCount++):m.logError("Internal error in bidmanager.addBidResponse. Params: "+e+" & "+t)}else r=this.createEmptyBidResponseObj();C[e]=r,this.checkIfAllBidsAreIn(e)},t.createEmptyBidResponseObj=function(){return{bids:[],allBidsAvailable:!1,bidsReceivedCount:0}},t.getKeyValueTargetingPairs=function(e,t){var r={},i=pbjs.bidderSettings||{};return e&&t&&i&&i[e]&&i[e][g.JSON_MAPPING.ADSERVER_TARGETING]?(o(r,i[e],t),t.alwaysUseBid=i[e].alwaysUseBid):D[e]?(o(r,D[e],t),t.alwaysUseBid=D[e].alwaysUseBid):t&&i&&(i[g.JSON_MAPPING.BD_SETTING_STANDARD]||(i[g.JSON_MAPPING.BD_SETTING_STANDARD]={adserverTargeting:[{key:"hb_bidder",val:function(e){return e.bidderCode}},{key:"hb_adid",val:function(e){return e.adId}},{key:"hb_pb",val:function(e){return e.pbMg}},{key:"hb_size",val:function(e){return e.size}}]}),o(r,i[g.JSON_MAPPING.BD_SETTING_STANDARD],t)),r},t.registerDefaultBidderSetting=function(e,t){D[e]=t},t.registerBidRequestTime=function(e,t){P[e]=t},t.executeCallback=function(){if(typeof pbjs.registerBidCallbackHandler===b&&!R)try{pbjs.registerBidCallbackHandler(),R=!0}catch(e){R=!0,m.logError("Exception trying to execute callback handler registered : "+e.message)}if(_.called!==!0){var t=[];c(_,t),_.called=!0}if(w){var t=[],r=pbjs.getBidResponses();t.push(r),c(w,t),w=null}},t.allBidsBack=function(){return T},t.setBidderMap=function(e){B=e},t.checkIfAllBidsAreIn=function(e){T=l(),p(e),T&&this.executeCallback()},t.addOneTimeCallback=function(e){w=e},t.addCallback=function(e,t,r){t.id=e,g.CB.TYPE.ALL_BIDS_BACK===r?_.push(t):g.CB.TYPE.AD_UNIT_BIDS_BACK===r&&y.push(t)},h.on(g.EVENTS.BID_ADJUSTMENT,function(e){f(e)})},function(e,t,r){var i=r(3),n=r(4),a=Array.prototype.slice,d=i._map(n.EVENTS,function(e){return e}),o=[];e.exports=function(){function e(e,t){i.logMessage("Emitting event for: "+e),o.push({eventType:e,args:t}),i._each(r[e],function(e){if(e)try{e.apply(null,t)}catch(r){i.logError("Error executing handler:","events.js",r)}})}function t(e){return i.contains(d,e)}var r={},n={};return n.on=function(e,n){t(e)?(r[e]=r[e]||[],r[e].push(n)):i.logError("Wrong event name : "+e+" Valid event names :"+d)},n.emit=function(t){var r=a.call(arguments,1);e(t,r)},n.off=function(e,t,n){i.isEmpty(r[e])||i._each(r[e],function(e){null!==e[t]&&void 0!==e[t]&&("undefined"==typeof n||e[t]===n)&&(e[t]=null)})},n.get=function(){return r},n.getEvents=function(){var e=[];return i._each(o,function(t){var r=i.extend({},t);e.push(r)}),e},n}()},function(e,t,r){function i(e){function t(){switch(i){case 0:return"Pending";case 1:return"Bid available";case 2:return"Bid returned empty or error response";case 3:return"Bid timed out"}}var r=n.getUniqueIdentifierStr(),i=e||0;this.bidderCode="",this.width=0,this.height=0,this.statusMessage=t(),this.adId=r,this.getStatusCode=function(){return i},this.getSize=function(){return this.width+"x"+this.height}}var n=r(3);t.createBid=function(e){return new i(e)}},function(e,t,r){var i=r(3);t.loadScript=function(e,t){if(!e)return i.logError("Error attempting to request empty URL","adloader.js:loadScript"),void 0;var r=document.createElement("script");r.type="text/javascript",r.async=!0,t&&"function"==typeof t&&(r.readyState?r.onreadystatechange=function(){("loaded"==r.readyState||"complete"==r.readyState)&&(r.onreadystatechange=null,t())}:r.onload=function(){t()}),r.src=e;var n=document.getElementsByTagName("head");n=n.length?n:document.getElementsByTagName("body"),n.length&&(n=n[0],n.insertBefore(r,n.firstChild))},t.trackPixel=function(e){var t,r;return e&&"string"==typeof e?(t=e.indexOf("?")>0?"&":"?",r=e+t+"rnd="+Math.floor(1e7*Math.random()),(new Image).src=r,r):(i.logMessage("Missing or invalid pixelUrl."),void 0)}},function(e,t,r){var i=r(4),n=r(3),a=r(8),d=r(5),o=r(7),s=r(10),c=function(){function e(e,t){var r=n.getBidIdParamater("placementId",e.params),i=n.getBidIdParamater("memberId",e.params),a=n.getBidIdParamater("member",e.params),d=n.getBidIdParamater("invCode",e.params),o=n.getBidIdParamater("query",e.params),s=n.getBidIdParamater("referrer",e.params),c=n.getBidIdParamater("alt_referrer",e.params),u="http"+("https:"===document.location.protocol?"s://secure.adnxs.com/jpt?":"://ib.adnxs.com/jpt?");u=n.tryAppendQueryString(u,"callback","pbjs.handleAnCB"),u=n.tryAppendQueryString(u,"callback_uid",t),u=n.tryAppendQueryString(u,"psa","0"),u=n.tryAppendQueryString(u,"id",r),a?u=n.tryAppendQueryString(u,"member_id",a):i&&(u=n.tryAppendQueryString(u,"member_id",i),n.logMessage('appnexus.callBids: "memberId" will be deprecated soon. Please use "member" instead')),u=n.tryAppendQueryString(u,"code",d),u=n.tryAppendQueryString(u,"code",d);var p="",l=n.parseSizesInput(e.sizes),f=l.length;if(f>0&&(p="size="+l[0],f>1)){p+="&promo_sizes=";for(var g=1;f>g;g++)p+=l[g]+=",";p&&","===p.charAt(p.length-1)&&(p=p.slice(0,p.length-1))}p&&(u+=p+"&");var m=n.parseQueryStringParameters(o);m&&(u+=m);var h=n.extend({},e.params);delete h.placementId,delete h.memberId,delete h.invCode,delete h.query,delete h.referrer,delete h.alt_referrer,delete h.member;var b=n.parseQueryStringParameters(h);return b&&(u+=b),""===s&&(s=n.getTopWindowUrl()),u=n.tryAppendQueryString(u,"referrer",s),u=n.tryAppendQueryString(u,"alt_referrer",c),u.lastIndexOf("&")===u.length-1&&(u=u.substring(0,u.length-1)),n.logMessage("jpt request built: "+u),e.startTime=(new Date).getTime(),u}var r=s.createNew("appnexus");return r.callBids=function(t){var i=r.getBidderCode(),o=t.bids,s=o.length;d.setExpectedBidsCount(i,s);for(var c=0;s>c;c++){var u=o[c],p=n.getUniqueIdentifierStr();a.loadScript(e(u,p)),d.pbCallbackMap[p]=u}},pbjs.handleAnCB=function(e){var t;if(e&&e.callback_uid){var r,a=e.callback_uid,s="",c=d.getPlacementIdByCBIdentifer(a);c&&(t=c.bidder,s=c.placementCode,c.status=i.STATUS.GOOD),n.logMessage("JSONP callback function called for ad ID: "+a);var u=[];if(e.result&&e.result.cpm&&0!==e.result.cpm){r=parseInt(e.result.cpm,10),r/=1e4;var p=(e.result.ad,e.result.creative_id);u=o.createBid(1),u.creative_id=p,u.bidderCode=t,u.cpm=r,u.adUrl=e.result.ad,u.width=e.result.width,u.height=e.result.height,u.dealId=e.result.deal_id,d.addBidResponse(s,u)}else n.logMessage("No prebid response from AppNexus for placement code "+s),u=o.createBid(2),u.bidderCode=t,d.addBidResponse(s,u)}else n.logMessage("No prebid response for placement %%PLACEMENT%%")},{callBids:r.callBids,setBidderCode:r.setBidderCode,createNew:t.createNew,buildJPTCall:e}};t.createNew=function(){return new c}},function(e,t){function r(e){function t(e){n=e}function r(){return n}function i(){}var n=e;return{callBids:i,setBidderCode:t,getBidderCode:r}}t.createNew=function(e){return new r(e)}},function(e,t,r){var i=r(3),n=r(7),a=r(5),d=r(8),o=function(){function e(e){return(e||"alias").replace(l,"")+ ++y}function t(e){var t=b.createElement("DIV");return e&&e.length||(e="ad-placeholder-"+ ++_),t.id=e+"-head-unit",v.appendChild(t),t.id}function r(e,t){var r,d=h[t.placement];if(!d)return i.logError("mismatched bid: "+t.placement,g,t),void 0;if(r=e.getCPM(),null===r||isNaN(r))return o(e,t);var s=n.createBid(1);s.bidderCode=g,s.ad=e.getCreative()+e.getPixels(),s.cpm=r,s.width=e.getAdWidth(),s.height=e.getAdHeight(),s.creativeId=e.getCreativeId(),a.addBidResponse(d.placementCode,s)}function o(e,t){var r=h[t.alias||t.placement];if(!r)return i.logError("mismatched bid: "+t.placement,g,t),void 0;var d=n.createBid(2);d.bidderCode=g,d.reason=e.getNbr(),d.raw=e.getResponse(),a.addBidResponse(r.placementCode,d)}function s(r){return h[r.params.placement]=r,{adContainerId:t(r.params.adContainerId),server:r.params.server,sizeid:r.params.sizeId||0,pageid:r.params.pageId,secure:!1,serviceType:"pubapi",performScreenDetection:!1,alias:r.params.alias||e(r.placementCode),network:r.params.network,placement:parseInt(r.params.placement),gpt:{adUnitPath:r.params.adUnitPath||r.placementCode,size:r.params.size||(r.sizes||[])[0]},params:{cors:"yes",cmd:"bid"},pubApiConfig:m,placementCode:r.placementCode}}function c(){return window.ADTECH?(i._each(p,function(e){var t=s(e);window.ADTECH.loadAd(t)}),void 0):(i.logError("window.ADTECH is not present!",g),void 0)}function u(e){p=e.bids,p&&p.length&&d.loadScript(f,c)}var p,l=/\W/g,f=window.location.protocol+"//aka-cdn.adtechus.com/dt/common/DAC.js",g="aol",m={pixelsDivId:"pixelsDiv",defaultKey:"aolBid",roundingConfig:[{from:0,to:999,roundFunction:"tenCentsRound"},{from:1e3,to:-1,roundValue:1e3}],pubApiOK:r,pubApiER:o},h={},b=window.document,v=b.getElementsByTagName("HEAD")[0],y=0,_=0;return{callBids:u}};e.exports=o},function(e,t,r){var i=(r(4),r(3),r(7)),n=r(5),a=r(8),d=function(e){function t(e){o=e.bids||[];for(var t=0;t
    ',d.adUrl=a.get("ad_url"),d.width=a.get("width"),d.height=a.get("height"),n.addBidResponse(r.placementCode,d)}else d=i.createBid(2),d.bidderCode="openx",n.addBidResponse(r.placementCode,d)},OX.Hooks.ON_AD_RESPONSE),t.load()})}var d,o,s=e||{};return{callBids:t}};e.exports=d},function(e,t,r){var i=(r(4),r(3)),n=r(7),a=r(5),d=(r(8),function(){function e(e){d=e.bids;for(var r=0;r',e+="",e+="",e=i.replaceTokenInString(e,t,"%%")}var d,o,s=[];return pbjs.handlePubmaticCallback=function(e){var t,r,i,o,s,c=e&&e.bidDetailsMap||{},u=e&&e.progKeyValueMap||{};for(t=0;t"},buildBid:function(t){var r={};if(t&&t.ybot_ad&&"n"!==t.ybot_ad){r=n.createBid(e.BID_STATUS.AVAILABLE),r.cpm=parseInt(t.ybot_cpm)/100||0;var i=t.ybot_size?t.ybot_size.split("x"):[0,0],a=t.ybot_slot||"",d=t.ybot_size||"";r.width=i[0]||0,r.height=i[1]||0,r.ad=e.buildCreative(a,d);for(var o in t)r[o]=t[o]}else r=n.createBid(e.BID_STATUS.EMPTY);return r.bidderCode="yieldbot",r},callBids:function(t){var r=t.bids||[],n=window.ybotq||[];e.pageLevelOption=!1,n.push(function(){var t=window.yieldbot;d._each(r,function(r){var i=r,n=i.params&&i.params.psn||"ERROR_DEFINE_YB_PSN",o=i.params&&i.params.slot||"ERROR_DEFINE_YB_SLOT";t.pub(n),t.defineSlot(o,{sizes:i.sizes||[]});var s=d.getUniqueIdentifierStr();a.pbCallbackMap[s]=i,e.definedSlots.push(s)}),t.enableAsync(),t.go()}),n.push(function(){e.handleUpdateState()}),i.loadScript("//cdn.yldbt.com/js/yieldbot.intent.js")},handleUpdateState:function(){var t=window.yieldbot;d._each(e.definedSlots,function(r){var i,n,d,o;o=a.getPlacementIdByCBIdentifer(r)||{},i=o.params.slot||"",n=t.getSlotCriteria(i),d=o.placementCode||"ERROR_YB_NO_PLACEMENT";var s=e.buildBid(n);a.addBidResponse(d,s)})}};return{callBids:e.callBids}};e.exports=o},function(e,t,r){var n=(r(4),r(3)),a=r(7),d=r(5),o=r(8),s="INDEXEXCHANGE",c="indexExchange",u=!0,p=function(){};window.cygnus_index_args={};var l=[[728,90],[120,600],[300,250],[160,600],[336,280],[234,60],[300,600],[300,50],[320,50],[970,250],[300,1050],[970,90],[180,150]],f=function(){function e(e){var t=n[e];return"string"==typeof t?t:"\\u"+("0000"+e.charCodeAt(0).toString(16)).slice(-4)}function t(t){return i.lastIndex=0,i.test(t)?t.replace(i,e):t}function r(e,t,r){if(this.initialized=!1,"number"!=typeof e||e%1!==0||0>e)throw"Invalid Site ID";if("number"==typeof r&&r%1===0&&r>=0&&(this.timeoutDelay=r),this.siteID=e,this.impressions=[],this._parseFnName=void 0,top===self?(this.sitePage=location.href,this.topframe=1):(this.sitePage=document.referrer,this.topframe=0),"undefined"!=typeof t){if("function"!=typeof t)throw"Invalid jsonp target function";this._parseFnName="cygnus_index_args.parseFn"}_IndexRequestData.requestCounter="undefined"==typeof _IndexRequestData.requestCounter?Math.floor(256*Math.random()):(_IndexRequestData.requestCounter+1)%256,this.requestID=String((new Date).getTime()%2592e3*256+_IndexRequestData.requestCounter+256),this.initialized=!0}cygnus_index_args.parseFn=p;var i=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,n={"\b":"\\b"," ":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"};r.prototype.serialize=function(){var e='{"id":'+this.requestID+',"site":{"page":"'+t(this.sitePage)+'"';"string"==typeof document.referrer&&(e+=',"ref":"'+t(document.referrer)+'"'),e+='},"imp":[';for(var r=0;r0&&(e+=',"ext": {'+n.join()+"}"),e+=r+1==this.impressions.length?"}":"},"}return e+="]}"},r.prototype.setPageOverride=function(e){return"string"!=typeof e||e.match(/^\s*$/)?!1:(this.sitePage=e,!0)},r.prototype.addImpression=function(e,t,r,i,n,a){var d={id:String(this.impressions.length+1)};if("number"!=typeof e||1>=e)return null;if("number"!=typeof t||1>=t)return null;if(("string"==typeof n||"number"==typeof n)&&String(n).length<=50&&(d.slotID=String(n)),d.w=e,d.h=t,void 0!==r&&"number"!=typeof r)return null;if("number"==typeof r){if(0>r)return null;if(d.bidfloor=r,void 0!==i&&"string"!=typeof i)return null;d.bidfloorcur=i}if("undefined"!=typeof a){if(!("number"==typeof a&&a%1===0&&a>=0))return null;d.siteID=a}return this.impressions.push(d),d.id},r.prototype.buildRequest=function(){if(0!==this.impressions.length&&this.initialized===!0){var e=encodeURIComponent(this.serialize()),t="https:"===window.location.protocol?"https://as-sec.casalemedia.com":"http://as.casalemedia.com";return t+="/headertag?v=9&x3=1&fn=cygnus_index_parse_res&s="+this.siteID+"&r="+e,"number"==typeof this.timeoutDelay&&this.timeoutDelay%1===0&&this.timeoutDelay>=0&&(t+="&t="+this.timeoutDelay),t}};try{if("undefined"==typeof cygnus_index_args||"undefined"==typeof cygnus_index_args.siteID||"undefined"==typeof cygnus_index_args.slots)return;"undefined"==typeof _IndexRequestData&&(_IndexRequestData={},_IndexRequestData.impIDToSlotID={},_IndexRequestData.reqOptions={});var a=new r(cygnus_index_args.siteID,cygnus_index_args.parseFn,cygnus_index_args.timeout);cygnus_index_args.url&&"string"==typeof cygnus_index_args.url&&a.setPageOverride(cygnus_index_args.url),_IndexRequestData.impIDToSlotID[a.requestID]={},_IndexRequestData.reqOptions[a.requestID]={};for(var d,o,s=0;s';s=d.createBid(1),s.creative_id=e.Id,s.bidderCode="sovrn",s.cpm=r,s.ad=decodeURIComponent(u+p);var l=c.sizes.length;2===l&&"number"==typeof c.sizes[0]&&"number"==typeof c.sizes[1]?(s.width=c.sizes[0],s.height=c.sizes[1]):(s.width=c.sizes[0][0],s.height=c.sizes[0][1]),o.addBidResponse(i,s)}else s=d.createBid(2),s.bidderCode="sovrn",o.addBidResponse(i,s);else s=d.createBid(2),s.bidderCode="sovrn",o.addBidResponse(i,s)}),c(t)}else c([]);else c([])},{callBids:e}};e.exports=c},function(e,t,r){var i=r(7),n=r(5),a=r(8),d=function(){function e(e){"undefined"==typeof window.pp?a.loadScript(o,function(){t(e)}):t(e)}function t(e){for(var t=e.bids,i=0;i0&&void 0!==e.seatbid[0].bid[0]){for(var t=e.seatbid[0].bid[0],r=n.getPlacementIdByCBIdentifer(t.impid),a=i.createBid(1),d=0;du;u++)i=s[u],i.adxDomain&&o&&(o=!1,c.unshift("//"+i.adxDomain+"/adx/?rp=4")),c.push(t(i.params));o&&c.unshift("//adx.adform.net/adx/?rp=4"),pbjs[d]=r(s),c.push("callback=pbjs."+d),a.loadScript(c.join("&"))}function t(e){for(var t,r=[],n=["mid","inv","pdom","mname","mkw","mkv","cat","bcat","bcatrt","adv","advt","cntr","cntrt","maxp","minp","sminp","w","h","pb","pos","cturl","iturl","cttype","hidedomain","cdims","test"],a=0,d=n.length;d>a;a++)t=n[a],e.hasOwnProperty(t)&&r.push(t,"=",e[t],"&");return i(r.join(""))}function r(e){function t(e,t){for(var r=0,i=t.length;i>r;r++)if(e.width===t[r][0]&&e.height===t[r][1])return!0;return!1}return function(r){for(var i,n,a,s="adform",c=0,u=r.length;u>c;c++)n=r[c],a=e[c],n&&"banner"===n.response&&t(n,a.sizes)?(i=o.createBid(1),i.bidderCode=s,i.cpm=n.win_bid,i.cur=n.win_cur,i.ad=n.banner,i.width=n.width,i.height=n.height,d.addBidResponse(a.placementCode,i)):(i=o.createBid(2),i.bidderCode=s,d.addBidResponse(a.placementCode,i))}}function i(e){var t,r,i,n,a,d,o,c=[],u=0,p="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_=";for(e=s(e);u>2,a=(3&t)<<4|r>>4,d=(15&r)<<2|i>>6,o=63&i,isNaN(r)?d=o=64:isNaN(i)&&(o=64),c.push(p.charAt(n),p.charAt(a)),64!=d&&c.push(p.charAt(d)),64!=o&&c.push(p.charAt(o));return c.join("")}function s(e){e=e.replace(/\r\n/g,"\n");for(var t="",r=0;ri?t+=String.fromCharCode(i):i>127&&2048>i?(t+=String.fromCharCode(i>>6|192),t+=String.fromCharCode(63&i|128)):(t+=String.fromCharCode(i>>12|224),t+=String.fromCharCode(i>>6&63|128),t+=String.fromCharCode(63&i|128))}return t}return{callBids:e}}var n=r(3),a=r(8),d=r(5),o=r(7);e.exports=i},function(e,t,r){function i(){if(w&&"function"==typeof window[_]){for(var e=0;e=0&&200>e?t="0-200ms":e>=200&&300>e?t="200-300ms":e>=300&&400>e?t="300-400ms":e>=400&&500>e?t="400-500ms":e>=500&&600>e?t="500-600ms":e>=600&&800>e?t="600-800ms":e>=800&&1e3>e?t="800-1000ms":e>=1e3&&1200>e?t="1000-1200ms":e>=1200&&1500>e?t="1200-1500ms":e>=1500&&2e3>e?t="1500-2000ms":e>=2e3&&(t="2000ms above"),t}function d(e){var t;return e>=0&&.5>e?t="$0-0.5":e>=.5&&1>e?t="$0.5-1":e>=1&&1.5>e?t="$1-1.5":e>=1.5&&2>e?t="$1.5-2":e>=2&&2.5>e?t="$2-2.5":e>=2.5&&3>e?t="$2.5-3":e>=3&&4>e?t="$3-4":e>=4&&6>e?t="$4-6":e>=6&&8>e?t="$6-8":e>=8&&(t="$8 above"),t}function o(e){e&&e.bidderCode&&y.push(function(){I++,window[_]("send","event",B,"Requests",e.bidderCode,1,v)}),i()}function s(e){e&&e.bidderCode&&y.push(function(){var t=n(e.cpm),r=e.bidderCode;if("undefined"!=typeof e.timeToRespond&&C){I++;var i=a(e.timeToRespond);window[_]("send","event","Prebid.js Load Time Distribution",i,r,1,v)}if(e.cpm>0){I+=2;var o=d(e.cpm);C&&(I++,window[_]("send","event","Prebid.js CPM Distribution",o,r,1,v)),window[_]("send","event",B,"Bids",r,t,v),window[_]("send","event",B,"Bid Load Time",r,e.timeToRespond,v)}}),i()}function c(e){e&&e.bidder&&y.push(function(){l._each(E,function(t){e.bidder===t&&(I++,window[_]("send","event",B,"Timeouts",t,e.timeToRespond,v))})}),i()}function u(e){var t=n(e.cpm);y.push(function(){I++,window[_]("send","event",B,"Wins",e.bidderCode,t,v)}),i()}var p=r(6),l=r(3),f=r(4),g=f.EVENTS.BID_REQUESTED,m=f.EVENTS.BID_TIMEOUT,h=f.EVENTS.BID_RESPONSE,b=f.EVENTS.BID_WON,v={nonInteraction:!0},y=[],_=null,w=!0,B="Prebid.js Bids",I=0,C=!1,E=[];t.enableAnalytics=function(e){_="undefined"!=typeof e.global?e.global:"ga","undefined"!=typeof e.enableDistribution&&(C=e.enableDistribution);var t=null,r=p.getEvents();l._each(r,function(e){var r=e.args;if(e)if(e.eventType===g)t=r[0],o(t);else if(e.eventType===h)t=r[1],s(t);else if(e.eventType===m){var i=r[0];E=i}else e.eventType===b&&(t=r[0],u(t))}),p.on(g,function(e){o(e)}),p.on(h,function(e,t){s(t),c(t)}),p.on(m,function(e){E=e}),p.on(b,function(e){u(e)})}},function(e,t,r){function i(){for(var e=0;e0&&i.push({cpm:d.cpm,bid:d}),t.push(n)}}if(r&&0!==i.length){var o=u(i),s=o.adserverTargeting;j[r]=_.extend(j[r],s)}return t}function g(e){var t={};if(e){var r=JSON.stringify(e);t=JSON.parse(r),delete t.pbLg,delete t.pbMg,delete t.pbHg}return t}function m(){w.clearAllBidResponses(),O={},N=[],j={},z=!1}function h(e){var t=e;m(),n(t)}function b(e){var t=null;return e&&(t=v.getAdserverTargetingForAdUnitCode(e.getSlotElementId()),t||(t=v.getAdserverTargetingForAdUnitCode(e.getAdUnitPath()))),t}window.pbjs=window.pbjs||{},window.pbjs.que=window.pbjs.que||[];var v=window.pbjs,y=r(4),_=r(3),w=r(5),B=r(1),I=r(7),C=r(8),E=r(20),S=r(6),A="function",T="undefined",R="object",D="string",P=y.EVENTS.BID_WON,x=y.EVENTS.BID_TIMEOUT,U=[],N=[],O={},j={},M={},z=!1;v.bidderTimeout=v.bidderTimeout||3e3,v.logging=v.logging||!1,v.libLoaded=!0,v.adUnits=v.adUnits||[],v.que.push=function(e){if(typeof e===A)try{e.call()}catch(t){_.logError("Error processing command :"+t.message)}else _.logError("Commands written into pbjs.que.push must wrapped in a function")},v.getAdserverTargetingForAdUnitCodeStr=function(e){if(e){var t=v.getAdserverTargetingForAdUnitCode(e);return _.transformAdServerTargetingObj(t)}_.logMessage("Need to call getAdserverTargetingForAdUnitCodeStr with adunitCode")},v.getAdserverTargetingForAdUnitCode=function(e){return v.getBidResponses(e),e?j[e]:j},v.getAdserverTargeting=function(){return v.getAdserverTargetingForAdUnitCode()},v.getBidResponses=function(e){var t={},r=[],i={};if(e)t=l(e),r=[],t&&t.bids&&(r=f(t.bids)),i={bids:r};else{t=l();for(var n in t)t.hasOwnProperty(n)&&(t&&t[n]&&t[n].bids&&(r=f(t[n].bids)),i[n]={bids:r})}return i},v.getBidResponsesForAdUnitCode=function(e){return v.getBidResponses(e)},v.setTargetingForAdUnitsGPTAsync=function(e){if(!window.googletag||!_.isFn(window.googletag.pubads)||!_.isFn(window.googletag.pubads().getSlots))return _.logError("window.googletag is not defined on the page"),void 0;d();var t=e;typeof e===D?t=[e]:typeof e===R&&(t=e);var r={},i=0;if(t)for(i=0;i'),e.close(),e.defaultView&&e.defaultView.frameElement&&(e.defaultView.frameElement.width=n,e.defaultView.frameElement.height=i)):_.logError("Error trying to write ad. No ad for bid response id: "+t)}else _.logError("Error trying to write ad. Cannot find ad by given id : "+t)}catch(o){_.logError("Error trying to write ad Id :"+t+" to the page:"+o.message)}else _.logError("Error trying to write ad Id :"+t+" to the page. Missing document or adId")},v.requestBidsForAdUnit=function(e){m(),n(e)},v.requestBidsForAdUnits=function(e){if(!e||e.constructor!==Array)return _.logError("requestBidsForAdUnits must pass an array of adUnits"),void 0;m();var t=v.adUnits.slice(0);v.adUnits=e,n(),v.adUnits=t},v.removeAdUnit=function(e){if(e)for(var t=0;t",a+='',a+="",a=n.replaceTokenInString(a,d,"%%")}var s={};return window.pbjs=window.pbjs||{que:[]},window.pbjs.handleRubiconCallback=function(e){var r="",o={};if(e&&"ok"===e.status)try{var c="",u=d.getPlacementIdByCBIdentifer(t(e));if(u&&(r=u.placementCode,u.status=i.STATUS.GOOD,c=u.iframeId),e.ads&&e.ads[0]&&"ok"===e.ads[0].status){o=a.createBid(1);var p,l=e.ads[0],f=s[l.size_id],g=0,m=0,h=window.frames[c];if(p=h.contentWindow?h.contentWindow.RubiconAdServing:h.window.RubiconAdServing,p&&p.AdSizes){f=p.AdSizes[l.size_id];var b=f.dim.split("x");g=b[0],m=b[1]}o.cpm=l.cpm,o.ad="",o.ad_id=l.ad_id,o.bidderCode="rubicon",o.sizeId=l.size_id,o.width=g,o.height=m}else{o=a.createBid(2),o.bidderCode="rubicon";var u=d.getPlacementIdByCBIdentifer(t(e));u&&(r=u.placementCode)}}catch(v){n.logError("Error parsing rubicon response bid: "+v.message)}else{o=a.createBid(2),o.bidderCode="rubicon";var u=d.getPlacementIdByCBIdentifer(t(e));u&&(r=u.placementCode)}d.addBidResponse(r,o)},{callBids:e}};e.exports=o}]); \ No newline at end of file From 6c2db87253e7b0794e438dd3a0f17755241f4503 Mon Sep 17 00:00:00 2001 From: Bart van Bragt Date: Mon, 15 Feb 2016 14:07:31 +0100 Subject: [PATCH 034/160] Small typo in AppNexus adapter --- src/adapters/appnexus.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/adapters/appnexus.js b/src/adapters/appnexus.js index a29dacf6f43..1c2f019c59d 100644 --- a/src/adapters/appnexus.js +++ b/src/adapters/appnexus.js @@ -19,11 +19,11 @@ var AppNexusAdapter = function AppNexusAdapter() { bidmanager.setExpectedBidsCount(bidCode,bidsCount); for (var i = 0; i < bidsCount; i++) { - var bidReqeust = anArr[i]; + var bidRequest = anArr[i]; var callbackId = utils.getUniqueIdentifierStr(); adloader.loadScript(buildJPTCall(bidReqeust, callbackId)); //store a reference to the bidRequest from the callback id - bidmanager.pbCallbackMap[callbackId] = bidReqeust; + bidmanager.pbCallbackMap[callbackId] = bidRequest; } }; @@ -218,4 +218,4 @@ var AppNexusAdapter = function AppNexusAdapter() { exports.createNew = function(){ return new AppNexusAdapter(); }; -// module.exports = AppNexusAdapter; \ No newline at end of file +// module.exports = AppNexusAdapter; From 5b498251c73997627ae24c4a33a5917b67e00813 Mon Sep 17 00:00:00 2001 From: mkendall07 Date: Tue, 16 Feb 2016 15:36:51 -0500 Subject: [PATCH 035/160] Fix typo in AppNexus adapter. --- src/adapters/appnexus.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/adapters/appnexus.js b/src/adapters/appnexus.js index 1c2f019c59d..7a49baa7221 100644 --- a/src/adapters/appnexus.js +++ b/src/adapters/appnexus.js @@ -7,7 +7,6 @@ var Adapter = require('./adapter.js'); var AppNexusAdapter = function AppNexusAdapter() { var baseAdapter = Adapter.createNew('appnexus'); - var isCalled = false; baseAdapter.callBids = function(params){ var bidCode = baseAdapter.getBidderCode(); @@ -21,7 +20,7 @@ var AppNexusAdapter = function AppNexusAdapter() { for (var i = 0; i < bidsCount; i++) { var bidRequest = anArr[i]; var callbackId = utils.getUniqueIdentifierStr(); - adloader.loadScript(buildJPTCall(bidReqeust, callbackId)); + adloader.loadScript(buildJPTCall(bidRequest, callbackId)); //store a reference to the bidRequest from the callback id bidmanager.pbCallbackMap[callbackId] = bidRequest; } From 935d2aa94b67e53a85f3f9882e5a712b394e5a3d Mon Sep 17 00:00:00 2001 From: mkendall07 Date: Tue, 16 Feb 2016 15:39:15 -0500 Subject: [PATCH 036/160] Gitignore coverage reports --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index a761f9ae904..56110b1dd50 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,9 @@ test/app # Dev File integrationExamples/gpt/gpt.html +# Coverage reports +build/coverage/ + # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion *.iml From 1fd041c8e8d86e298ad55df1491d1e83886b5cf0 Mon Sep 17 00:00:00 2001 From: protonate Date: Wed, 3 Feb 2016 17:30:27 -0800 Subject: [PATCH 037/160] Unit tests for Publisher API 50% coverage This brings test coverage of the Prebid.js API to > 50%. * add test for `getAdserverTargetingForAdUnitCodeStr()` * add test for `getAdserverTargetingForAdUnitCode()` * add test for `getBidResponses` * add test for `getBidResponsesForAdUnitCode` * add test for `setTargetingForGPTAsync` * add test for `allBidsAvailable` * update to .gitignore * additional fixtures * mock googletag methods, additional unit tests --- .gitignore | 4 + build/dev/prebid.js | 2 +- test/fixtures/ad-server-targeting.json | 12 ++ test/fixtures/bid-responses-cloned.json | 130 ++++++++++++++++++ test/fixtures/bid-responses.json | 155 ++++++++++++++++++++++ test/fixtures/config.json | 12 ++ test/fixtures/googletag-slots.json | 11 ++ test/fixtures/targeting-map.json | 8 ++ test/spec/unit/pbjs_api_spec.js | 167 ++++++++++++++++++++++++ 9 files changed, 500 insertions(+), 1 deletion(-) create mode 100644 test/fixtures/ad-server-targeting.json create mode 100644 test/fixtures/bid-responses-cloned.json create mode 100644 test/fixtures/bid-responses.json create mode 100644 test/fixtures/config.json create mode 100644 test/fixtures/googletag-slots.json create mode 100644 test/fixtures/targeting-map.json create mode 100644 test/spec/unit/pbjs_api_spec.js diff --git a/.gitignore b/.gitignore index a761f9ae904..04f3e94e9fb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,15 @@ # Built Files node_modules/ +build/coverage # Test Files test/app +gpt.html # Dev File + integrationExamples/gpt/gpt.html +integrationExamples/implementations/ # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion diff --git a/build/dev/prebid.js b/build/dev/prebid.js index 3c7dcfec68b..dd2bc5cd730 100644 --- a/build/dev/prebid.js +++ b/build/dev/prebid.js @@ -1,5 +1,5 @@ /* prebid.js v0.6.0 -Updated : 2016-02-11 */ +Updated : 2016-02-16 */ /******/ (function(modules) { // webpackBootstrap /******/ // The module cache /******/ var installedModules = {}; diff --git a/test/fixtures/ad-server-targeting.json b/test/fixtures/ad-server-targeting.json new file mode 100644 index 00000000000..9c6774c1ed5 --- /dev/null +++ b/test/fixtures/ad-server-targeting.json @@ -0,0 +1,12 @@ +{ + "/9968336/header-bid-tag-0": { + "hb_bidder": "rubicon", + "hb_adid": "13f44b0d3c", + "hb_pb": "1.50" + }, + "/9968336/header-bid-tag1": { + "hb_bidder": "openx", + "hb_adid": "147ac541a", + "hb_pb": "1.00" + } +} diff --git a/test/fixtures/bid-responses-cloned.json b/test/fixtures/bid-responses-cloned.json new file mode 100644 index 00000000000..188518b4565 --- /dev/null +++ b/test/fixtures/bid-responses-cloned.json @@ -0,0 +1,130 @@ +{ + "/123456/header-bid-tag-1": { + "bids": [] + }, + "/123456/header-bid-tag-0": { + "bids": [ + { + "bidderCode": "criteo", + "width": 0, + "height": 0, + "statusMessage": "Bid returned empty or error response", + "adId": "83fb6a073", + "requestTimestamp": 1454535718619, + "responseTimestamp": 1454535720575, + "timeToRespond": 1956, + "cpm": 0, + "adUnitCode": "/123456/header-bid-tag-0", + "bidder": "criteo" + }, + { + "bidderCode": "sovrn", + "width": 0, + "height": 0, + "statusMessage": "Bid returned empty or error response", + "adId": "9d64dde8c", + "requestTimestamp": 1454535718628, + "responseTimestamp": 1454535721135, + "timeToRespond": 2507, + "cpm": 0, + "adUnitCode": "/123456/header-bid-tag-0", + "bidder": "sovrn" + }, + { + "bidderCode": "pulsepoint", + "width": 0, + "height": 0, + "statusMessage": "Bid returned empty or error response", + "adId": "102e25872d", + "requestTimestamp": 1454535718629, + "responseTimestamp": 1454535721687, + "timeToRespond": 3058, + "cpm": 0, + "adUnitCode": "/123456/header-bid-tag-0", + "bidder": "pulsepoint" + }, + { + "bidderCode": "amazon", + "width": 0, + "height": 0, + "statusMessage": "Bid available", + "adId": "112cdb3eff", + "adUnitCode": "/123456/header-bid-tag-0", + "bidder": "amazon" + }, + { + "bidderCode": "yieldbot", + "width": 0, + "height": 0, + "statusMessage": "Bid returned empty or error response", + "adId": "1234cc92d8", + "requestTimestamp": 1454535718624, + "responseTimestamp": 1454535722273, + "timeToRespond": 3649, + "cpm": 0, + "adUnitCode": "/123456/header-bid-tag-0", + "bidder": "yieldbot" + }, + { + "bidderCode": "openx", + "width": 0, + "height": 0, + "statusMessage": "Bid returned empty or error response", + "adId": "1383ffde21", + "requestTimestamp": 1454535718611, + "responseTimestamp": 1454535724228, + "timeToRespond": 5617, + "cpm": 0, + "adUnitCode": "/123456/header-bid-tag-0", + "bidder": "openx" + }, + { + "bidderCode": "rubicon", + "width": "300", + "height": "250", + "statusMessage": "Bid available", + "adId": "148018fe5e", + "cpm": 0.537234, + "ad": "", + "ad_id": "3163950", + "sizeId": "15", + "requestTimestamp": 1454535718610, + "responseTimestamp": 1454535724863, + "timeToRespond": 6253, + "adUnitCode": "/123456/header-bid-tag-0", + "bidder": "rubicon", + "size": "300x250", + "adserverTargeting": { + "hb_bidder": "rubicon", + "hb_adid": "148018fe5e", + "hb_pb": "10.00", + "foobar": "300x250" + } + }, + { + "bidderCode": "pubmatic", + "width": "300", + "height": "250", + "statusMessage": "Bid available", + "adId": "15bea0b1db", + "adSlot": "39620189@300x250", + "cpm": 0.01, + "ad": "\n ", + "adUrl": "http://aktrack.pubmatic.com/AdServer/AdDisplayTrackerServlet?operId=1&pubId=39741&siteId=66156&adId=148827&adServerId=243&kefact=0.010000&kaxefact=0.010000&kadNetFrequecy=1&kadwidth=300&kadheight=250&kadsizeid=9&kltstamp=1454535719&indirectAdId=0&adServerOptimizerId=2&ranreq=0.052495126612484455&kpbmtpfact=0.011000&dcId=1&tldId=13890466&passback=0&imprId=529C7210-AB7A-4217-A9BD-A3190CA2382A&oid=529C7210-AB7A-4217-A9BD-A3190CA2382A&ias=272&fbs=1&campaignId=5400&creativeId=0&pctr=0.000000&wDSPByrId=1&pageURL=http%3A%2F%2Flocalhost%3A9999%2FintegrationExamples%2Fgpt%2Fgpt.html&lpu=www.xfinity.com", + "dealId": "", + "requestTimestamp": 1454535718617, + "responseTimestamp": 1454535725437, + "timeToRespond": 6820, + "adUnitCode": "/123456/header-bid-tag-0", + "bidder": "pubmatic", + "size": "300x250", + "adserverTargeting": { + "hb_bidder": "pubmatic", + "hb_adid": "15bea0b1db", + "hb_pb": "10.00", + "foobar": "300x250" + } + } + ] + } +} \ No newline at end of file diff --git a/test/fixtures/bid-responses.json b/test/fixtures/bid-responses.json new file mode 100644 index 00000000000..35e55664cdc --- /dev/null +++ b/test/fixtures/bid-responses.json @@ -0,0 +1,155 @@ +{ + "/123456/header-bid-tag-1": { + "bids": [], + "allBidsAvailable": false, + "bidsReceivedCount": 0 + }, + "/123456/header-bid-tag-0": { + "bids": [ + { + "bidderCode": "criteo", + "width": 0, + "height": 0, + "statusMessage": "Bid returned empty or error response", + "adId": "83fb6a073", + "requestTimestamp": 1454535718619, + "responseTimestamp": 1454535720575, + "timeToRespond": 1956, + "cpm": 0, + "pbLg": "", + "pbMg": "", + "pbHg": "", + "adUnitCode": "/123456/header-bid-tag-0", + "bidder": "criteo" + }, + { + "bidderCode": "sovrn", + "width": 0, + "height": 0, + "statusMessage": "Bid returned empty or error response", + "adId": "9d64dde8c", + "requestTimestamp": 1454535718628, + "responseTimestamp": 1454535721135, + "timeToRespond": 2507, + "cpm": 0, + "pbLg": "", + "pbMg": "", + "pbHg": "", + "adUnitCode": "/123456/header-bid-tag-0", + "bidder": "sovrn" + }, + { + "bidderCode": "pulsepoint", + "width": 0, + "height": 0, + "statusMessage": "Bid returned empty or error response", + "adId": "102e25872d", + "requestTimestamp": 1454535718629, + "responseTimestamp": 1454535721687, + "timeToRespond": 3058, + "cpm": 0, + "pbLg": "", + "pbMg": "", + "pbHg": "", + "adUnitCode": "/123456/header-bid-tag-0", + "bidder": "pulsepoint" + }, + { + "bidderCode": "amazon", + "width": 0, + "height": 0, + "statusMessage": "Bid available", + "adId": "112cdb3eff", + "adUnitCode": "/123456/header-bid-tag-0", + "bidder": "amazon" + }, + { + "bidderCode": "yieldbot", + "width": 0, + "height": 0, + "statusMessage": "Bid returned empty or error response", + "adId": "1234cc92d8", + "requestTimestamp": 1454535718624, + "responseTimestamp": 1454535722273, + "timeToRespond": 3649, + "cpm": 0, + "pbLg": "", + "pbMg": "", + "pbHg": "", + "adUnitCode": "/123456/header-bid-tag-0", + "bidder": "yieldbot" + }, + { + "bidderCode": "openx", + "width": 0, + "height": 0, + "statusMessage": "Bid returned empty or error response", + "adId": "1383ffde21", + "requestTimestamp": 1454535718611, + "responseTimestamp": 1454535724228, + "timeToRespond": 5617, + "cpm": 0, + "pbLg": "", + "pbMg": "", + "pbHg": "", + "adUnitCode": "/123456/header-bid-tag-0", + "bidder": "openx" + }, + { + "bidderCode": "rubicon", + "width": "300", + "height": "250", + "statusMessage": "Bid available", + "adId": "148018fe5e", + "cpm": 0.537234, + "ad": "", + "ad_id": "3163950", + "sizeId": "15", + "requestTimestamp": 1454535718610, + "responseTimestamp": 1454535724863, + "timeToRespond": 6253, + "pbLg": "0.50", + "pbMg": "0.50", + "pbHg": "0.53", + "adUnitCode": "/123456/header-bid-tag-0", + "bidder": "rubicon", + "size": "300x250", + "adserverTargeting": { + "hb_bidder": "rubicon", + "hb_adid": "148018fe5e", + "hb_pb": "10.00", + "foobar": "300x250" + } + }, + { + "bidderCode": "pubmatic", + "width": "300", + "height": "250", + "statusMessage": "Bid available", + "adId": "15bea0b1db", + "adSlot": "39620189@300x250", + "cpm": 0.01, + "ad": "\n ", + "adUrl": "http://aktrack.pubmatic.com/AdServer/AdDisplayTrackerServlet?operId=1&pubId=39741&siteId=66156&adId=148827&adServerId=243&kefact=0.010000&kaxefact=0.010000&kadNetFrequecy=1&kadwidth=300&kadheight=250&kadsizeid=9&kltstamp=1454535719&indirectAdId=0&adServerOptimizerId=2&ranreq=0.052495126612484455&kpbmtpfact=0.011000&dcId=1&tldId=13890466&passback=0&imprId=529C7210-AB7A-4217-A9BD-A3190CA2382A&oid=529C7210-AB7A-4217-A9BD-A3190CA2382A&ias=272&fbs=1&campaignId=5400&creativeId=0&pctr=0.000000&wDSPByrId=1&pageURL=http%3A%2F%2Flocalhost%3A9999%2FintegrationExamples%2Fgpt%2Fgpt.html&lpu=www.xfinity.com", + "dealId": "", + "requestTimestamp": 1454535718617, + "responseTimestamp": 1454535725437, + "timeToRespond": 6820, + "pbLg": "0.00", + "pbMg": "0.00", + "pbHg": "0.01", + "adUnitCode": "/123456/header-bid-tag-0", + "bidder": "pubmatic", + "size": "300x250", + "adserverTargeting": { + "hb_bidder": "pubmatic", + "hb_adid": "15bea0b1db", + "hb_pb": "10.00", + "foobar": "300x250" + } + } + ], + "allBidsAvailable": false, + "bidsReceivedCount": 8 + } +} \ No newline at end of file diff --git a/test/fixtures/config.json b/test/fixtures/config.json new file mode 100644 index 00000000000..5b842813923 --- /dev/null +++ b/test/fixtures/config.json @@ -0,0 +1,12 @@ +{ + "adUnitElementIDs": [ + "div-test-ad-0", + "div-test-ad-1", + "div-test-ad-2" + ], + "adUnitCodes": [ + "/123456/header-bid-tag-0", + "/123456/header-bid-tag-1", + "/123456/header-bid-tag-2" + ] +} \ No newline at end of file diff --git a/test/fixtures/googletag-slots.json b/test/fixtures/googletag-slots.json new file mode 100644 index 00000000000..4c1347ba5e5 --- /dev/null +++ b/test/fixtures/googletag-slots.json @@ -0,0 +1,11 @@ +[ + { + "getSlotElementId": "function(){ return \"div-gpt-ad-1438287399331-0\"; }" + }, + { + "getSlotElementId": "function(){ return \"div-gpt-ad-1438287399331-1\"; }" + }, + { + "getSlotElementId": "function(){ return \"div-gpt-ad-1438287399331-2\"; }" + } +] \ No newline at end of file diff --git a/test/fixtures/targeting-map.json b/test/fixtures/targeting-map.json new file mode 100644 index 00000000000..373e538a08e --- /dev/null +++ b/test/fixtures/targeting-map.json @@ -0,0 +1,8 @@ +{ + "/123456/header-bid-tag-0": { + "hb_bidder": "rubicon", + "hb_adid": "148018fe5e", + "hb_pb": "10.00", + "foobar": "300x250" + } +} \ No newline at end of file diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js new file mode 100644 index 00000000000..0abd62a8076 --- /dev/null +++ b/test/spec/unit/pbjs_api_spec.js @@ -0,0 +1,167 @@ +var assert = require('chai').assert; + +var prebid = require('src/prebid'); +var utils = require('src/utils'); +var bidmanager = require('src/bidmanager'); + +var bidResponses = require('test/fixtures/bid-responses.json'); +var targetingMap = require('test/fixtures/targeting-map.json'); +var config = require('test/fixtures/config.json'); +var targetingString = 'hb_bidder=rubicon&hb_adid=148018fe5e&hb_pb=10.00&foobar=300x250&'; +var spyLogMessage = sinon.spy(utils, 'logMessage'); + +var Slot = function Slot(elementId, pathId) { + var slot = { + getSlotElementId: function getSlotElementId() { + return elementId; + }, + getAdUnitPath: function getAdUnitPath() { + return pathId; + }, + setTargeting: function setTargeting(key, value) { + } + }; + slot.spySetTargeting = sinon.spy(slot, 'setTargeting'); + return slot; +}; + +var createSlotArray = function createSlotArray() { + return [ + new Slot(config.adUnitElementIDs[0], config.adUnitCodes[0]), + new Slot(config.adUnitElementIDs[1], config.adUnitCodes[1]), + new Slot(config.adUnitElementIDs[2], config.adUnitCodes[2]) + ]; +}; + +window.googletag = { + _slots: [], + pubads: function () { + var self = this; + return { + getSlots: function () { + return self._slots; + }, + setSlots: function (slots) { + self._slots = slots; + } + } + } +}; + +bidmanager.pbBidResponseByPlacement = bidResponses; + +after(function () { + utils.logMessage.restore(); +}); + +describe('Unit: Prebid API', function () { + describe('getAdserverTargetingForAdUnitCodeStr', function () { + it('should return targeting info as a string', function () { + var result = pbjs.getAdserverTargetingForAdUnitCodeStr(config.adUnitCodes[0]); + assert.equal(result, targetingString, 'returns expected string of ad targeting info') + }); + + it('should log message if adunitCode param is falsey', function () { + var result = pbjs.getAdserverTargetingForAdUnitCodeStr(); + assert.ok(spyLogMessage.calledWith('Need to call getAdserverTargetingForAdUnitCodeStr with adunitCode'), 'expected message was logged'); + assert.equal(result, undefined, 'result is undefined'); + }); + }); + + describe('getAdserverTargetingForAdUnitCode', function () { + it('should return targeting info as an object', function () { + var result = pbjs.getAdserverTargetingForAdUnitCode(config.adUnitCodes[0]); + assert.deepEqual(result, targetingMap[config.adUnitCodes[0]], 'returns expected targeting info object'); + }); + it('should return full targeting map object if adunitCode is falsey', function () { + var result = pbjs.getAdserverTargetingForAdUnitCode(); + assert.deepEqual(result, targetingMap, 'the complete targeting map object is returned'); + }); + }); + + describe('getAdServerTargeting', function () { + it('should call getAdServerTargetingForAdUnitCode', function () { + var spyGetAdServerTargetingForAdUnitCode = sinon.spy(pbjs, 'getAdserverTargetingForAdUnitCode'); + pbjs.getAdserverTargeting(); + assert.ok(spyGetAdServerTargetingForAdUnitCode.calledOnce, 'called the expected function'); + pbjs.getAdserverTargetingForAdUnitCode.restore(); + }); + }); + + describe('getBidResponses', function () { + it('should return expected bid responses when passed an adunitCode', function () { + var result = pbjs.getBidResponses(config.adUnitCodes[0]); + var compare = require('test/fixtures/bid-responses-cloned.json')[config.adUnitCodes[0]]; + + assert.deepEqual(result, compare); + }); + it('should return expected bid responses when not passed an adunitCode', function () { + var result = pbjs.getBidResponses(); + var compare = require('test/fixtures/bid-responses-cloned.json'); + + assert.deepEqual(result, compare); + }); + }); + + describe('getBidResponsesForAdUnitCode', function () { + it('should call getBidResponses with passed in adUnitCode', function () { + var adUnitCode = 'xyz'; + var spyGetBidResponses = sinon.spy(pbjs, 'getBidResponses'); + + pbjs.getBidResponsesForAdUnitCode(adUnitCode); + assert.ok(spyGetBidResponses.calledWith(adUnitCode)); + pbjs.getBidResponses.restore(); + }); + }); + + describe('setTargetingForGPTAsync', function () { + it('should log a message when googletag functions not defined', function() { + var pubads = window.googletag.pubads; + + window.googletag.pubads = undefined; + pbjs.setTargetingForAdUnitsGPTAsync(); + spyLogMessage.calledWith('window.googletag is not defined on the page'); + window.googletag.pubads = pubads; + }); + + it('should set targeting when passed an array of ad unit codes', function () { + var slots = createSlotArray(); + window.googletag.pubads().setSlots(slots); + + pbjs.setTargetingForGPTAsync(config.adUnitCodes); + assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_bidder', ''), 'clears hb_bidder param'); + assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_adid', ''), 'clears hb_adid param'); + assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_pb', ''), 'clears hb_pb param'); + assert.ok(slots[0].spySetTargeting.calledWithExactly('foobar', ''), 'clears foobar param'); + assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_bidder', 'rubicon'), 'sets hb_bidder param'); + assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_adid', '148018fe5e'), 'sets hb_adid param'); + assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_pb', '10.00'), 'sets hb_pb param'); + assert.ok(slots[0].spySetTargeting.calledWithExactly('foobar', '300x250'), 'sets foobar param'); + }); + + it('should set targeting from googletag data', function () { + var slots = createSlotArray(); + window.googletag.pubads().setSlots(slots); + + pbjs.setTargetingForGPTAsync(); + assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_bidder', ''), 'clears hb_bidder param'); + assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_adid', ''), 'clears hb_adid param'); + assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_pb', ''), 'clears hb_pb param'); + assert.ok(slots[0].spySetTargeting.calledWithExactly('foobar', ''), 'clears foobar param'); + assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_bidder', 'rubicon'), 'sets hb_bidder param'); + assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_adid', '148018fe5e'), 'sets hb_adid param'); + assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_pb', '10.00'), 'sets hb_pb param'); + assert.ok(slots[0].spySetTargeting.calledWithExactly('foobar', '300x250'), 'sets foobar param'); + }); + }); + + describe('allBidsAvailable', function() { + it('should call bidmanager.allBidsBack', function() { + var spyAllBidsBack = sinon.spy(bidmanager, 'allBidsBack'); + + pbjs.allBidsAvailable(); + assert.ok(spyAllBidsBack.called, 'called bidmanager.allBidsBack'); + bidmanager.allBidsBack.restore(); + }); + }); +}); From 93a5aaecdad595d033858c848e41ec0074e60688 Mon Sep 17 00:00:00 2001 From: protonate Date: Wed, 17 Feb 2016 12:06:41 -0800 Subject: [PATCH 038/160] New API methods `onEvent()` and `offEvent()` This commit supplies two new API methods to configure event callbacks. Events are 'bidRequested','bidResponse','bidWon','bidTimeout'. Updated with code review fixes. --- build/dev/prebid.js | 4732 --------------------------------- build/dist/prebid.js | 2 - src/constants.json | 6 +- src/events.js | 213 +- src/polyfills.js | 70 + src/prebid.js | 79 +- src/utils.js | 13 +- styleRules.jscs.json | 2 +- test/spec/unit/events_spec.js | 27 - 9 files changed, 286 insertions(+), 4858 deletions(-) delete mode 100644 build/dev/prebid.js delete mode 100644 build/dist/prebid.js create mode 100644 src/polyfills.js delete mode 100644 test/spec/unit/events_spec.js diff --git a/build/dev/prebid.js b/build/dev/prebid.js deleted file mode 100644 index dd2bc5cd730..00000000000 --- a/build/dev/prebid.js +++ /dev/null @@ -1,4732 +0,0 @@ -/* prebid.js v0.6.0 -Updated : 2016-02-16 */ -/******/ (function(modules) { // webpackBootstrap -/******/ // The module cache -/******/ var installedModules = {}; - -/******/ // The require function -/******/ function __webpack_require__(moduleId) { - -/******/ // Check if module is in cache -/******/ if(installedModules[moduleId]) -/******/ return installedModules[moduleId].exports; - -/******/ // Create a new module (and put it into the cache) -/******/ var module = installedModules[moduleId] = { -/******/ exports: {}, -/******/ id: moduleId, -/******/ loaded: false -/******/ }; - -/******/ // Execute the module function -/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); - -/******/ // Flag the module as loaded -/******/ module.loaded = true; - -/******/ // Return the exports of the module -/******/ return module.exports; -/******/ } - - -/******/ // expose the modules object (__webpack_modules__) -/******/ __webpack_require__.m = modules; - -/******/ // expose the module cache -/******/ __webpack_require__.c = installedModules; - -/******/ // __webpack_public_path__ -/******/ __webpack_require__.p = ""; - -/******/ // Load entry module and return exports -/******/ return __webpack_require__(0); -/******/ }) -/************************************************************************/ -/******/ ([ -/* 0 */ -/***/ function(module, exports, __webpack_require__) { - - __webpack_require__(1); - __webpack_require__(8); - __webpack_require__(7); - __webpack_require__(5); - __webpack_require__(6); - __webpack_require__(20); - __webpack_require__(21); - __webpack_require__(3); - __webpack_require__(10); - __webpack_require__(19); - __webpack_require__(11); - __webpack_require__(9); - __webpack_require__(15); - __webpack_require__(12); - __webpack_require__(13); - __webpack_require__(17); - __webpack_require__(2); - __webpack_require__(22); - __webpack_require__(16); - __webpack_require__(18); - module.exports = __webpack_require__(14); - - -/***/ }, -/* 1 */ -/***/ function(module, exports, __webpack_require__) { - - /** @module adaptermanger */ - - var RubiconAdapter = __webpack_require__(2); - var AppNexusAdapter = __webpack_require__(9); - var AolAdapter = __webpack_require__(11); - var OpenxAdapter = __webpack_require__(12); - var PubmaticAdapter = __webpack_require__(13); - var YieldbotAdapter = __webpack_require__(14); - var IndexExchange = __webpack_require__(15); - var Sovrn = __webpack_require__(16); - var PulsePointAdapter = __webpack_require__(17); - var SpringServeAdapter = __webpack_require__(18); - var AdformAdapter = __webpack_require__(19); - var bidmanager = __webpack_require__(5); - var utils = __webpack_require__(3); - var CONSTANTS = __webpack_require__(4); - var events = __webpack_require__(6); - - var _bidderRegistry = {}; - exports.bidderRegistry = _bidderRegistry; - - - exports.callBids = function(bidderArr) { - for (var i = 0; i < bidderArr.length; i++) { - //use the bidder code to identify which function to call - var bidder = bidderArr[i]; - if (bidder.bidderCode && _bidderRegistry[bidder.bidderCode]) { - utils.logMessage('CALLING BIDDER ======= ' + bidder.bidderCode); - var currentBidder = _bidderRegistry[bidder.bidderCode]; - //emit 'bidRequested' event - events.emit(CONSTANTS.EVENTS.BID_REQUESTED, bidder); - currentBidder.callBids(bidder); - - // if the bidder didn't explicitly set the number of bids - // expected, default to the number of bids passed into the bidder - if (bidmanager.getExpectedBidsCount(bidder.bidderCode) === undefined) { - bidmanager.setExpectedBidsCount(bidder.bidderCode, bidder.bids.length); - } - - var currentTime = new Date().getTime(); - bidmanager.registerBidRequestTime(bidder.bidderCode, currentTime); - - if (currentBidder.defaultBidderSettings) { - bidmanager.registerDefaultBidderSetting(bidder.bidderCode, currentBidder.defaultBidderSettings); - } - } - else{ - utils.logError('Adapter trying to be called which does not exist: ' + bidder.bidderCode, 'adaptermanager.callBids'); - } - } - }; - - - exports.registerBidAdapter = function(bidAdaptor, bidderCode) { - if (bidAdaptor && bidderCode) { - - if (typeof bidAdaptor.callBids === CONSTANTS.objectType_function) { - _bidderRegistry[bidderCode] = bidAdaptor; - - } else { - utils.logError('Bidder adaptor error for bidder code: ' + bidderCode + 'bidder must implement a callBids() function'); - } - - } else { - utils.logError('bidAdaptor or bidderCode not specified'); - } - }; - - exports.aliasBidAdapter = function(bidderCode, alias){ - var existingAlias = _bidderRegistry[alias]; - - if(typeof existingAlias === CONSTANTS.objectType_undefined){ - var bidAdaptor = _bidderRegistry[bidderCode]; - - if(typeof bidAdaptor === CONSTANTS.objectType_undefined){ - utils.logError('bidderCode "' + bidderCode + '" is not an existing bidder.', 'adaptermanager.aliasBidAdapter'); - }else{ - try{ - var newAdapter = bidAdaptor.createNew(); - newAdapter.setBidderCode(alias); - this.registerBidAdapter(newAdapter,alias); - }catch(e){ - utils.logError(bidderCode + ' bidder does not currently support aliasing.', 'adaptermanager.aliasBidAdapter'); - } - } - }else{ - utils.logMessage('alias name "' + alias + '" has been already specified.'); - } - }; - - - // Register the bid adaptors here - this.registerBidAdapter(RubiconAdapter(), 'rubicon'); - this.registerBidAdapter(AppNexusAdapter.createNew(), 'appnexus'); - this.registerBidAdapter(OpenxAdapter(), 'openx'); - this.registerBidAdapter(PubmaticAdapter(), 'pubmatic'); - this.registerBidAdapter(YieldbotAdapter(), 'yieldbot'); - this.registerBidAdapter(IndexExchange(), 'indexExchange'); - this.registerBidAdapter(SpringServeAdapter(), 'springserve'); - this.registerBidAdapter(Sovrn(),'sovrn'); - this.registerBidAdapter(AolAdapter(), 'aol'); - this.registerBidAdapter(PulsePointAdapter(),'pulsepoint'); - //default bidder alias - this.aliasBidAdapter('appnexus', 'brealtime'); - this.registerBidAdapter(AdformAdapter(), 'adform'); - - -/***/ }, -/* 2 */ -/***/ function(module, exports, __webpack_require__) { - - /** - * @file Rubicon (Rubicon) adapter - */ - var utils = __webpack_require__(3); - var bidmanager = __webpack_require__(5); - var bidfactory = __webpack_require__(7); - var adloader = __webpack_require__(8); - - /** - * @class RubiconAdapter - * Prebid adapter for Rubicon's header bidding client - */ - var RubiconAdapter = function RubiconAdapter() { - var RUBICONTAG_URL = (window.location.protocol) + '//ads.rubiconproject.com/header/'; - var RUBICON_OK_STATUS = 'ok'; - var RUBICON_BIDDER_CODE = 'rubicon'; - var RUBICON_SIZE_MAP = { - "728x90": 2, - "160x600": 9, - "300x600": 10, - "300x250": 15, - "320x50": 43, - "300x1050": 54, - "970x250": 57 - }; - var RUBICON_INITIALIZED = 0; - - // the fastlane creative code - var RUBICON_CREATIVE_START = ''; - - // pre-initialize the rubicon object - // needs to be attached to the window - window.rubicontag = window.rubicontag || {}; - window.rubicontag.cmd = window.rubicontag.cmd || []; - - // timestamp for logging - var _bidStart = null; - var bidCount = 0; - - /** - * Create an error bid - * @param {String} placement - the adunit path - * @param {Object} response - the (error) response from fastlane - * @return {Bid} a bid, for prebid - */ - function _errorBid(response, ads) { - var bidResponse = bidfactory.createBid(2); - bidResponse.bidderCode = RUBICON_BIDDER_CODE; - - // use the raw ads as the 'error' - bidResponse.error = ads; - return bidResponse; - } - - /** - * Sort function for CPM - * @param {Object} adA - * @param {Object} adB - * @return {Float} sort order value - */ - function _adCpmSort(adA, adB) { - return (adB.cpm || 0.0) - (adA.cpm || 0.0); - } - - /** - * Produce the code to render a creative - * @param {String} elemId the element passed to rubicon; this is essentially the ad-id - * @param {Array} size array of width, height - * @return {String} creative - */ - function _creative(elemId, size) { - - // convert the size to a rubicon sizeId - var sizeId = RUBICON_SIZE_MAP[size.join('x')]; - - if (!sizeId) { - utils.logError( - 'fastlane: missing sizeId for size: ' + size.join('x') + ' could not render creative', - RUBICON_BIDDER_CODE, RUBICON_SIZE_MAP); - return ''; - } - - return RUBICON_CREATIVE_START + elemId + '", "' + sizeId + RUBICON_CREATIVE_END; - } - - /** - * Create a (successful) bid for a unit, - * based on the given response - * @param {String} placement placement code/unit path - * @param {Object} response the response from rubicon - * @return {Bid} a bid objectj - */ - function _makeBid(response, ads) { - - // if there are multiple ads, sort by CPM - ads = ads.sort(_adCpmSort); - - var bidResponse = bidfactory.createBid(1), - ad = ads[0], - size = ad.dimensions; - - if (!size) { - // this really shouldn't happen - utils.logError('no dimensions given', RUBICON_BIDDER_CODE, ad); - return _errorBid(response, ads); - } - - bidResponse.bidderCode = RUBICON_BIDDER_CODE; - bidResponse.cpm = ad.cpm; - // the element id is what the iframe will use to render - // itself using the rubicontag.renderCreative API - bidResponse.ad = _creative(response.getElementId(), size); - bidResponse.width = size[0]; - bidResponse.height = size[1]; - return bidResponse; - } - - /** - * Add a success/error bid based - * on the response from rubicon - * @param {Object} response -- AJAX response from fastlane - */ - function _addBid(response, ads) { - // get the bid for the placement code - var bid; - if (!ads || ads.length === 0) { - bid = _errorBid(response, ads); - } else { - bid = _makeBid(response, ads); - } - - bidmanager.addBidResponse(response.getSlotName(), bid); - } - - /** - * Helper to queue functions on rubicontag - * ready/available - * @param {Function} callback - */ - function _rready(callback) { - window.rubicontag.cmd.push(callback); - } - - /** - * download the rubicontag sdk - * @param {Object} options - * @param {String} options.accountId - * @param {Function} callback - */ - function _initSDK(options, done) { - if (RUBICON_INITIALIZED) return; - RUBICON_INITIALIZED = 1; - var accountId = options.accountId; - adloader.loadScript(RUBICONTAG_URL + accountId + '.js', done); - } - - /** - * map the sizes in `bid.sizes` to Rubicon specific keys - * @param {object} array of bids - * @return {[type]} [description] - */ - function _mapSizes(bids){ - utils._each(bids, function(bid){ - if(bid.params.sizes){ - return; - } - //return array like ['300x250', '728x90'] - var parsedSizes = utils.parseSizesInput(bid.sizes); - //iterate the bid.sizes array to lookup codes - var tempSize = []; - for(var i =0; i < parsedSizes.length; i++){ - var rubiconKey = RUBICON_SIZE_MAP[parsedSizes[i]]; - if(rubiconKey){ - tempSize.push(rubiconKey); - } - } - bid.params.sizes = tempSize; - }); - } - - /** - * Define the slot using the rubicontag.defineSlot API - * @param {Object} Bidrequest - */ - function _defineSlot(bid) { - _rready(function () { - var newSlot=window.rubicontag.defineSlot({ - siteId: bid.params.siteId, - zoneId: bid.params.zoneId, - id: bid.placementCode, - sizes: bid.params.sizes - }); - if (bid.params.position) { - newSlot.setPosition(bid.params.position); - } - if (bid.params.userId) { - window.rubicontag.setUserKey(bid.params.userId); - } - if (bid.params.keywords) { - for(var i=0; i < bid.params.keywords.length; i++){ - newSlot.addKW(bid.params.keywords[i]); - } - } - if (bid.params.inventory) { - for (var p in bid.params.inventory) { - if (bid.params.inventory.hasOwnProperty(p)) { - newSlot.addFPI(p,bid.params.inventory[p]); - } - } - } - if (bid.params.visitor) { - for (var p in bid.params.visitor) { - if (bid.params.visitor.hasOwnProperty(p)) { - newSlot.addFPV(p,bid.params.visitor[p]); - } - } - } - }); - } - - /** - * Handle the bids received (from rubicon) - */ - function _bidsReady() { - // NOTE: we don't really need to do anything, - // because right now we're shimming XMLHttpRequest.open, - // but in the future we'll get data from rubicontag here - utils.logMessage('Rubicon Project bidding complete: ' + ((new Date).getTime() - _bidStart)); - - utils._each(rubicontag.getAllSlots(), function (slot) { - _addBid(slot, slot.getRawResponses()); - }); - } - - - /** - * Request the specified bids from - * Rubicon - * @param {Object} params the bidder-level params (from prebid) - * @param {Array} params.bids the bids requested - */ - function _callBids(params) { - - // start the timer; want to measure from - // even just loading the SDK - _bidStart = (new Date).getTime(); - - _mapSizes(params.bids); - - utils._each(params.bids, function (bid, index) { - // on the first bid, set up the SDK - // the config will be set on each bid - if (index === 0) { - _initSDK(bid.params); - } - - _defineSlot(bid); - }); - - _rready(function () { - window.rubicontag.run(_bidsReady); - }); - } - - return { - /** - * @public callBids - * the interface to Prebid - */ - callBids: _callBids - }; - }; - - module.exports = RubiconAdapter; - - -/***/ }, -/* 3 */ -/***/ function(module, exports, __webpack_require__) { - - var CONSTANTS = __webpack_require__(4); - var objectType_function = 'function'; - var objectType_undefined = 'undefined'; - var objectType_object = 'object'; - var objectType_string = 'string'; - var objectType_number = 'number'; - - var _loggingChecked = false; - - var _lgPriceCap = 5.00; - var _mgPriceCap = 20.00; - var _hgPriceCap = 20.00; - - var t_Arr = 'Array', - t_Str = 'String', - t_Fn = 'Function', - toString = Object.prototype.toString, - hasOwnProperty = Object.prototype.hasOwnProperty, - slice = Array.prototype.slice; - - /* - * Substitues into a string from a given map using the token - * Usage - * var str = 'text %%REPLACE%% this text with %%SOMETHING%%'; - * var map = {}; - * map['replace'] = 'it was subbed'; - * map['something'] = 'something else'; - * console.log(replaceTokenInString(str, map, '%%')); => "text it was subbed this text with something else" - */ - exports.replaceTokenInString = function(str, map, token) { - this._each(map, function (value, key) { - value = (value === undefined) ? '' : value; - - var keyString = token + key.toUpperCase() + token, - re = new RegExp(keyString, 'g'); - - str = str.replace(re, value); - }); - return str; - }; - - /* utility method to get incremental integer starting from 1 */ - var getIncrementalInteger = (function() { - var count = 0; - return function() { - count++; - return count; - }; - })(); - - function _getUniqueIdentifierStr() { - return getIncrementalInteger() + Math.random().toString(16).substr(2); - } - - //generate a random string (to be used as a dynamic JSONP callback) - exports.getUniqueIdentifierStr = _getUniqueIdentifierStr; - - exports.getBidIdParamater = function(key, paramsObj) { - if (paramsObj && paramsObj[key]) { - return paramsObj[key]; - } - return ''; - }; - - exports.tryAppendQueryString = function(existingUrl, key, value) { - if (value) { - return existingUrl += key + '=' + encodeURIComponent(value) + '&'; - } - return existingUrl; - }; - - - //parse a query string object passed in bid params - //bid params should be an object such as {key: "value", key1 : "value1"} - exports.parseQueryStringParameters = function(queryObj) { - var result = ""; - for (var k in queryObj){ - if (queryObj.hasOwnProperty(k)) - result += k + "=" + encodeURIComponent(queryObj[k]) + "&"; - } - return result; - }; - - - //transform an AdServer targeting bids into a query string to send to the adserver - //bid params should be an object such as {key: "value", key1 : "value1"} - exports.transformAdServerTargetingObj = function(adServerTargeting) { - var result = ""; - if (!adServerTargeting) - return ""; - for (var k in adServerTargeting) - if (adServerTargeting.hasOwnProperty(k)) - result += k + "=" + encodeURIComponent(adServerTargeting[k]) + "&"; - return result; - }; - - //Copy all of the properties in the source objects over to the target object - //return the target object. - exports.extend = function(target, source){ - target = target || {}; - - this._each(source,function(value,prop){ - if (typeof source[prop] === objectType_object) { - target[prop] = this.extend(target[prop], source[prop]); - } else { - target[prop] = source[prop]; - } - }); - return target; - }; - - /** - * Parse a GPT-Style general size Array like `[[300, 250]]` or `"300x250,970x90"` into an array of sizes `["300x250"]` or '['300x250', '970x90']' - * @param {array[array|number]} sizeObj Input array or double array [300,250] or [[300,250], [728,90]] - * @return {array[string]} Array of strings like `["300x250"]` or `["300x250", "728x90"]` - */ - exports.parseSizesInput = function(sizeObj) { - var parsedSizes = []; - - //if a string for now we can assume it is a single size, like "300x250" - if (typeof sizeObj === objectType_string) { - //multiple sizes will be comma-separated - var sizes = sizeObj.split(','); - //regular expression to match strigns like 300x250 - //start of line, at least 1 number, an "x" , then at least 1 number, and the then end of the line - var sizeRegex = /^(\d)+x(\d)+$/i; - if (sizes) { - for (var curSizePos in sizes) { - if (hasOwn(sizes, curSizePos) && sizes[curSizePos].match(sizeRegex)) { - parsedSizes.push(sizes[curSizePos]); - } - } - } - } else if (typeof sizeObj === objectType_object) { - var sizeArrayLength = sizeObj.length; - //don't process empty array - if (sizeArrayLength > 0) { - //if we are a 2 item array of 2 numbers, we must be a SingleSize array - if (sizeArrayLength === 2 && typeof sizeObj[0] === objectType_number && typeof sizeObj[1] === objectType_number) { - parsedSizes.push(this.parseGPTSingleSizeArray(sizeObj)); - } else { - //otherwise, we must be a MultiSize array - for (var i = 0; i < sizeArrayLength; i++) { - parsedSizes.push(this.parseGPTSingleSizeArray(sizeObj[i])); - } - - } - } - } - - return parsedSizes; - - }; - - //parse a GPT style sigle size array, (i.e [300,250]) - //into an AppNexus style string, (i.e. 300x250) - exports.parseGPTSingleSizeArray = function(singleSize) { - //if we aren't exactly 2 items in this array, it is invalid - if (this.isArray(singleSize) && singleSize.length === 2 && (!isNaN(singleSize[0]) && !isNaN(singleSize[1]))) { - return singleSize[0] + 'x' + singleSize[1]; - } - }; - - exports.getTopWindowUrl = function() { - try { - return window.top.location.href; - } catch (e) { - return window.location.href; - } - }; - - exports.logMessage = function(msg) { - if (debugTurnedOn() && hasConsoleLogger()) { - console.log('MESSAGE: ' + msg); - } - }; - - function hasConsoleLogger() { - return (window.console && window.console.log); - } - exports.hasConsoleLogger = hasConsoleLogger; - - var errLogFn = (function (hasLogger) { - if (!hasLogger) return ''; - return window.console.error ? 'error' : 'log'; - }(hasConsoleLogger())); - - var debugTurnedOn = function() { - if (pbjs.logging === false && _loggingChecked === false) { - pbjs.logging = getParameterByName(CONSTANTS.DEBUG_MODE).toUpperCase() === 'TRUE'; - _loggingChecked = true; - } - - if (pbjs.logging) { - return true; - } - return false; - - }; - exports.debugTurnedOn = debugTurnedOn; - - exports.logError = function(msg, code, exception) { - var errCode = code || 'ERROR'; - if (debugTurnedOn() && hasConsoleLogger()) { - console[errLogFn].call(console, errCode + ': ' + msg, exception || ''); - } - }; - - exports.createInvisibleIframe = function _createInvisibleIframe() { - var f = document.createElement('iframe'); - f.id = _getUniqueIdentifierStr(); - f.height = 0; - f.width = 0; - f.border = '0px'; - f.hspace = '0'; - f.vspace = '0'; - f.marginWidth = '0'; - f.marginHeight = '0'; - f.style.border = '0'; - f.scrolling = 'no'; - f.frameBorder = '0'; - f.src = 'about:self'; - f.style = 'display:none'; - return f; - }; - - /* - * Check if a given paramater name exists in query string - * and if it does return the value - */ - var getParameterByName = function(name) { - var regexS = '[\\?&]' + name + '=([^&#]*)', - regex = new RegExp(regexS), - results = regex.exec(window.location.search); - if (results === null) { - return ''; - } - return decodeURIComponent(results[1].replace(/\+/g, ' ')); - }; - - exports.getPriceBucketString = function(cpm) { - var low = '', - med = '', - high = '', - cpmFloat = 0, - returnObj = { - low: low, - med: med, - high: high - }; - try { - cpmFloat = parseFloat(cpm); - if (cpmFloat) { - //round to closet .5 - if (cpmFloat > _lgPriceCap) { - returnObj.low = _lgPriceCap.toFixed(2); - } else { - returnObj.low = (Math.floor(cpm * 2) / 2).toFixed(2); - } - - //round to closet .1 - if (cpmFloat > _mgPriceCap) { - returnObj.med = _mgPriceCap.toFixed(2); - } else { - returnObj.med = (Math.floor(cpm * 10) / 10).toFixed(2); - } - - //round to closet .01 - if (cpmFloat > _hgPriceCap) { - returnObj.high = _hgPriceCap.toFixed(2); - } else { - returnObj.high = (Math.floor(cpm * 100) / 100).toFixed(2); - } - } - } catch (e) { - this.logError('Exception parsing CPM :' + e.message); - } - return returnObj; - - }; - - /** - * This function validates paramaters. - * @param {object[string]} paramObj [description] - * @param {string[]} requiredParamsArr [description] - * @return {bool} Bool if paramaters are valid - */ - exports.hasValidBidRequest = function(paramObj, requiredParamsArr, adapter){ - - for(var i = 0; i < requiredParamsArr.length; i++){ - var found = false; - - this._each(paramObj, function (value, key) { - if (key === requiredParamsArr[i]) { - found = true; - } - }); - - if(!found){ - this.logError('Params are missing for bid request. One of these required paramaters are missing: ' + requiredParamsArr, adapter); - return false; - } - } - - return true; - }; - - // Handle addEventListener gracefully in older browsers - exports.addEventHandler = function(element, event, func) { - if (element.addEventListener) { - element.addEventListener(event, func, true); - } else if (element.attachEvent) { - element.attachEvent('on' + event, func); - } - }; - /** - * Return if the object is of the - * given type. - * @param {*} object to test - * @param {String} _t type string (e.g., Array) - * @return {Boolean} if object is of type _t - */ - exports.isA = function(object, _t) { - return toString.call(object) === '[object ' + _t + ']'; - }; - - exports.isFn = function (object) { - return this.isA(object, t_Fn); - }; - - exports.isStr = function (object) { - return this.isA(object, t_Str); - }; - - exports.isArray = function (object) { - return this.isA(object, t_Arr); - }; - - /** - * Return if the object is "empty"; - * this includes falsey, no keys, or no items at indices - * @param {*} object object to test - * @return {Boolean} if object is empty - */ - exports.isEmpty = function(object) { - if (!object) return true; - if (this.isArray(object) || this.isStr(object)) return !(object.length > 0); - for (var k in object) { - if (hasOwnProperty.call(object, k)) return false; - } - return true; - }; - - /** - * Iterate object with the function - * falls back to es5 `forEach` - * @param {Array|Object} object - * @param {Function(value, key, object)} fn - */ - exports._each = function(object, fn) { - if (this.isEmpty(object)) return; - if (this.isFn(object.forEach)) return object.forEach(fn, this); - - var k = 0, - l = object.length; - - if (l > 0) { - for (; k < l; k++) fn(object[k], k, object); - } else { - for (k in object) { - if (hasOwnProperty.call(object, k)) fn.call(this, object[k], k); - } - } - }; - - exports.contains = function(a, obj) { - if(this.isEmpty(a)){ - return false; - } - if (this.isFn(a.indexOf)) { - return a.indexOf(obj) !== -1; - } - var i = a.length; - while (i--) { - if (a[i] === obj) { - return true; - } - } - return false; - }; - - /** - * Map an array or object into another array - * given a function - * @param {Array|Object} object - * @param {Function(value, key, object)} callback - * @return {Array} - */ - exports._map = function (object, callback) { - if (this.isEmpty(object)) return []; - if (this.isFn(object.map)) return object.map(callback); - var output = []; - this._each(object, function (value, key) { - output.push(callback(value, key, object)); - }); - return output; - }; - - var hasOwn = function(objectToCheck, propertyToCheckFor) { - if (objectToCheck.hasOwnProperty) { - return objectToCheck.hasOwnProperty(propertyToCheckFor); - } else { - return (typeof objectToCheck[propertyToCheckFor] !== UNDEFINED) && (objectToCheck.constructor.prototype[propertyToCheckFor] !== objectToCheck[propertyToCheckFor]); - } - }; - -/***/ }, -/* 4 */ -/***/ function(module, exports) { - - module.exports = { - "JSON_MAPPING": { - "PL_CODE": "code", - "PL_SIZE": "sizes", - "PL_BIDS": "bids", - "BD_BIDDER": "bidder", - "BD_ID": "paramsd", - "BD_PL_ID": "placementId", - "ADSERVER_TARGETING": "adserverTargeting", - "BD_SETTING_STANDARD": "standard" - }, - "DEBUG_MODE": "pbjs_debug", - "STATUS": { - "GOOD": "good", - "TIMEOUT": "timed out" - }, - "CB": { - "TYPE": { - "ALL_BIDS_BACK": "allRequestedBidsBack", - "AD_UNIT_BIDS_BACK": "adUnitBidsBack" - } - }, - "objectType_function": "function", - "objectType_undefined": "undefined", - "objectType_object": "object", - "objectType_string": "string", - "objectType_number": "number", - "EVENTS": { - "BID_ADJUSTMENT": "bidAdjustment", - "BID_TIMEOUT": "bidTimeout", - "BID_REQUESTED": "bidRequested", - "BID_RESPONSE": "bidResponse", - "BID_WON": "bidWon" - } - }; - -/***/ }, -/* 5 */ -/***/ function(module, exports, __webpack_require__) { - - var CONSTANTS = __webpack_require__(4); - var utils = __webpack_require__(3); - var adaptermanager = __webpack_require__(1); - var events = __webpack_require__(6); - - var objectType_function = 'function'; - var objectType_undefined = 'undefined'; - - var externalCallbackByAdUnitArr = []; - var externalCallbackArr = []; - var externalOneTimeCallback = null; - var biddersByPlacementMap = {}; - - var pbCallbackMap = {}; - exports.pbCallbackMap = pbCallbackMap; - - var pbBidResponseByPlacement = {}; - exports.pbBidResponseByPlacement = pbBidResponseByPlacement; - - //this is used to look up the bid by bid ID later - var _adResponsesByBidderId = {}; - exports._adResponsesByBidderId = _adResponsesByBidderId; - - var bidResponseReceivedCount = {}; - exports.bidResponseReceivedCount = bidResponseReceivedCount; - - var expectedBidsCount = {}; - - var _allBidsAvailable = false; - - var _callbackExecuted = false; - - var defaultBidderSettingsMap = {}; - var bidderStartTimes = {}; - - exports.getPlacementIdByCBIdentifer = function(id) { - return pbCallbackMap[id]; - }; - - - exports.getBidResponseByAdUnit = function(adUnitCode) { - return pbBidResponseByPlacement; - - }; - - - exports.clearAllBidResponses = function(adUnitCode) { - _allBidsAvailable = false; - _callbackExecuted = false; - - //init bid response received count - initbidResponseReceivedCount(); - //init expected bids count - initExpectedBidsCount(); - //clear the callback handler flag - externalCallbackArr.called = false; - - for (var prop in this.pbBidResponseByPlacement) { - delete this.pbBidResponseByPlacement[prop]; - } - }; - - /** - * Returns a list of bidders that we haven't received a response yet - * @return {array} [description] - */ - exports.getTimedOutBidders = function(){ - var bidderArr = []; - utils._each(bidResponseReceivedCount,function(count,bidderCode){ - if(count === 0){ - bidderArr.push(bidderCode); - } - }); - - return bidderArr; - }; - - function initbidResponseReceivedCount(){ - - bidResponseReceivedCount = {}; - - for(var i=0; i 0 ? '&' : '?'; - //add a cachebuster so we don't end up dropping any impressions - trackingPixel = pixelUrl + delimiter + 'rnd=' + Math.floor(Math.random() * 1E7); - (new Image()).src = trackingPixel; - return trackingPixel; - }; - - -/***/ }, -/* 9 */ -/***/ function(module, exports, __webpack_require__) { - - var CONSTANTS = __webpack_require__(4); - var utils = __webpack_require__(3); - var adloader = __webpack_require__(8); - var bidmanager = __webpack_require__(5); - var bidfactory = __webpack_require__(7); - var Adapter = __webpack_require__(10); - - var AppNexusAdapter = function AppNexusAdapter() { - var baseAdapter = Adapter.createNew('appnexus'); - var isCalled = false; - - baseAdapter.callBids = function(params){ - var bidCode = baseAdapter.getBidderCode(); - - var anArr = params.bids; - var bidsCount = anArr.length; - - //set expected bids count for callback execution - bidmanager.setExpectedBidsCount(bidCode,bidsCount); - - for (var i = 0; i < bidsCount; i++) { - var bidReqeust = anArr[i]; - var callbackId = utils.getUniqueIdentifierStr(); - adloader.loadScript(buildJPTCall(bidReqeust, callbackId)); - //store a reference to the bidRequest from the callback id - bidmanager.pbCallbackMap[callbackId] = bidReqeust; - } - }; - - - function buildJPTCall(bid, callbackId) { - - //determine tag params - var placementId = utils.getBidIdParamater('placementId', bid.params); - //memberId will be deprecated, use member instead - var memberId = utils.getBidIdParamater('memberId', bid.params); - var member = utils.getBidIdParamater('member', bid.params); - var inventoryCode = utils.getBidIdParamater('invCode', bid.params); - var query = utils.getBidIdParamater('query', bid.params); - var referrer = utils.getBidIdParamater('referrer', bid.params); - var altReferrer = utils.getBidIdParamater('alt_referrer', bid.params); - - //build our base tag, based on if we are http or https - - var jptCall = 'http' + ('https:' === document.location.protocol ? 's://secure.adnxs.com/jpt?' : '://ib.adnxs.com/jpt?'); - - jptCall = utils.tryAppendQueryString(jptCall, 'callback', 'pbjs.handleAnCB'); - jptCall = utils.tryAppendQueryString(jptCall, 'callback_uid', callbackId); - jptCall = utils.tryAppendQueryString(jptCall, 'psa', '0'); - jptCall = utils.tryAppendQueryString(jptCall, 'id', placementId); - if(member){ - jptCall = utils.tryAppendQueryString(jptCall, 'member_id', member); - }else if(memberId){ - jptCall = utils.tryAppendQueryString(jptCall, 'member_id', memberId); - utils.logMessage('appnexus.callBids: "memberId" will be deprecated soon. Please use "member" instead'); - } - jptCall = utils.tryAppendQueryString(jptCall, 'code', inventoryCode); - jptCall = utils.tryAppendQueryString(jptCall, 'code', inventoryCode); - - - - //sizes takes a bit more logic - var sizeQueryString = ''; - var parsedSizes = utils.parseSizesInput(bid.sizes); - - //combine string into proper querystring for impbus - var 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 (var 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 + '&'; - } - - //this will be deprecated soon - var targetingParams = utils.parseQueryStringParameters(query); - - if (targetingParams) { - //don't append a & here, we have already done it in parseQueryStringParameters - jptCall += targetingParams; - } - - //append custom attributes: - var paramsCopy = utils.extend({}, bid.params); - //delete attributes already used - delete paramsCopy.placementId; - delete paramsCopy.memberId; - delete paramsCopy.invCode; - delete paramsCopy.query; - delete paramsCopy.referrer; - delete paramsCopy.alt_referrer; - delete paramsCopy.member; - - //get the reminder - var 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); - } - - // @if NODE_ENV='debug' - utils.logMessage('jpt request built: ' + jptCall); - // @endif - - //append a timer here to track latency - bid.startTime = new Date().getTime(); - - return jptCall; - - } - - //expose the callback to the global object: - pbjs.handleAnCB = function(jptResponseObj) { - - var bidCode; - - if (jptResponseObj && jptResponseObj.callback_uid) { - - var error; - var responseCPM; - var id = jptResponseObj.callback_uid, - placementCode = '', - //retrieve bid object by callback ID - bidObj = bidmanager.getPlacementIdByCBIdentifer(id); - if (bidObj) { - - bidCode = bidObj.bidder; - - placementCode = bidObj.placementCode; - //set the status - bidObj.status = CONSTANTS.STATUS.GOOD; - } - - // @if NODE_ENV='debug' - utils.logMessage('JSONP callback function called for ad ID: ' + id); - // @endif - var bid = []; - if (jptResponseObj.result && jptResponseObj.result.cpm && jptResponseObj.result.cpm !== 0) { - responseCPM = parseInt(jptResponseObj.result.cpm, 10); - - //CPM response from /jpt is dollar/cent multiplied by 10000 - //in order to avoid using floats - //switch CPM to "dollar/cent" - responseCPM = responseCPM / 10000; - var responseAd = jptResponseObj.result.ad; - //store bid response - //bid status is good (indicating 1) - var adId = jptResponseObj.result.creative_id; - bid = bidfactory.createBid(1); - 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 = jptResponseObj.result.deal_id; - - bidmanager.addBidResponse(placementCode, bid); - - - } else { - //no response data - // @if NODE_ENV='debug' - utils.logMessage('No prebid response from AppNexus for placement code ' + placementCode); - // @endif - //indicate that there is no bid for this placement - bid = bidfactory.createBid(2); - bid.bidderCode = bidCode; - bidmanager.addBidResponse(placementCode, bid); - } - - - - } else { - //no response data - // @if NODE_ENV='debug' - utils.logMessage('No prebid response for placement %%PLACEMENT%%'); - // @endif - - } - - }; - - return { - callBids: baseAdapter.callBids, - setBidderCode: baseAdapter.setBidderCode, - createNew: exports.createNew, - buildJPTCall : buildJPTCall - }; - }; - - exports.createNew = function(){ - return new AppNexusAdapter(); - }; - // module.exports = AppNexusAdapter; - -/***/ }, -/* 10 */ -/***/ function(module, exports) { - - function Adapter(code){ - var bidderCode = code; - - function setBidderCode(code){ - bidderCode = code; - } - - function getBidderCode(){ - return bidderCode; - } - - function callBids(){ - } - - return { - callBids: callBids, - setBidderCode: setBidderCode, - getBidderCode: getBidderCode - }; - } - - exports.createNew = function(bidderCode){ - return new Adapter(bidderCode); - }; - -/***/ }, -/* 11 */ -/***/ function(module, exports, __webpack_require__) { - - var utils = __webpack_require__(3), - bidfactory = __webpack_require__(7), - bidmanager = __webpack_require__(5), - adloader = __webpack_require__(8); - - var AolAdapter = function AolAdapter() { - - // constants - var ADTECH_PLACEMENT_RXP = /\W/g, - ADTECH_URI = (window.location.protocol) + '//aka-cdn.adtechus.com/dt/common/DAC.js', - ADTECH_BIDDER_NAME = 'aol', - ADTECH_PUBAPI_CONFIG = { - pixelsDivId: 'pixelsDiv', - defaultKey: 'aolBid', - roundingConfig: [{ - from: 0, - to: 999, - roundFunction: 'tenCentsRound' - }, { - from: 1000, - to: -1, - roundValue: 1000 - }], - pubApiOK: _addBid, - pubApiER: _addErrorBid - }; - - var bids, - bidsMap = {}, - d = window.document, - h = d.getElementsByTagName('HEAD')[0], - aliasCount = 0, - dummyUnitIdCount = 0; - - /** - * @private Given a placementCode slot path/div id - * for a unit, return a unique alias - * @param {String} placementCode - * @return {String} alias - */ - function _generateAlias(placementCode) { - return (placementCode || 'alias').replace(ADTECH_PLACEMENT_RXP, '') + (++aliasCount); - } - - /** - * @private create a div that we'll use as the - * location for the AOL unit; AOL will document.write - * if the div is not present in the document. - * @param {String} id to identify the div - * @return {String} the id used with the div - */ - function _dummyUnit(id) { - var div = d.createElement('DIV'); - - if (!id || !id.length) { - id = 'ad-placeholder-' + (++dummyUnitIdCount); - } - - div.id = id + '-head-unit'; - h.appendChild(div); - return div.id; - } - - /** - * @private Add a succesful bid response for aol - * @param {ADTECHResponse} response the response for the bid - * @param {ADTECHContext} context the context passed from aol - */ - function _addBid(response, context) { - var bid = bidsMap[context.placement], - cpm; - - if (!bid) { - utils.logError('mismatched bid: ' + context.placement, ADTECH_BIDDER_NAME, context); - return; - } - - cpm = response.getCPM(); - if (cpm === null || isNaN(cpm)) { - return _addErrorBid(response, context); - } - - var bidResponse = bidfactory.createBid(1); - bidResponse.bidderCode = ADTECH_BIDDER_NAME; - bidResponse.ad = response.getCreative() + response.getPixels(); - bidResponse.cpm = cpm; - bidResponse.width = response.getAdWidth(); - bidResponse.height = response.getAdHeight(); - bidResponse.creativeId = response.getCreativeId(); - - // add it to the bid manager - bidmanager.addBidResponse(bid.placementCode, bidResponse); - } - - /** - * @private Add an error bid response for aol - * @param {ADTECHResponse} response the response for the bid - * @param {ADTECHContext} context the context passed from aol - */ - function _addErrorBid(response, context) { - var bid = bidsMap[context.alias || context.placement]; - - if (!bid) { - utils.logError('mismatched bid: ' + context.placement, ADTECH_BIDDER_NAME, context); - return; - } - - var bidResponse = bidfactory.createBid(2); - bidResponse.bidderCode = ADTECH_BIDDER_NAME; - bidResponse.reason = response.getNbr(); - bidResponse.raw = response.getResponse(); - bidmanager.addBidResponse(bid.placementCode, bidResponse); - } - - - /** - * @private map a prebid bidrequest to an ADTECH/aol bid request - * @param {Bid} bid the bid request - * @return {Object} the bid request, formatted for the ADTECH/DAC api - */ - function _mapUnit(bid) { - // save the bid - bidsMap[bid.params.placement] = bid; - - return { - adContainerId: _dummyUnit(bid.params.adContainerId), - server: bid.params.server, // By default, DAC.js will use the US region endpoint (adserver.adtechus.com) - sizeid: bid.params.sizeId || 0, - pageid: bid.params.pageId, - secure: false, - serviceType: 'pubapi', - performScreenDetection: false, - alias: bid.params.alias || _generateAlias(bid.placementCode), - network: bid.params.network, - placement: parseInt(bid.params.placement), - gpt: { - adUnitPath: bid.params.adUnitPath || bid.placementCode, - size: bid.params.size || (bid.sizes || [])[0] - }, - params: { - cors: 'yes', - cmd: 'bid' - }, - pubApiConfig: ADTECH_PUBAPI_CONFIG, - placementCode: bid.placementCode - }; - } - - /** - * @private once ADTECH is loaded, request bids by - * calling ADTECH.loadAd - */ - function _reqBids() { - if (!window.ADTECH) { - utils.logError('window.ADTECH is not present!', ADTECH_BIDDER_NAME); - return; - } - - // get the bids - utils._each(bids, function(bid) { - var bidreq = _mapUnit(bid); - window.ADTECH.loadAd(bidreq); - }); - } - - /** - * @public call the bids - * this requests the specified bids - * from aol marketplace - * @param {Object} params - * @param {Array} params.bids the bids to be requested - */ - function _callBids(params) { - bids = params.bids; - if (!bids || !bids.length) return; - adloader.loadScript(ADTECH_URI, _reqBids); - } - - return { - callBids: _callBids - }; - }; - - module.exports = AolAdapter; - -/***/ }, -/* 12 */ -/***/ function(module, exports, __webpack_require__) { - - var CONSTANTS = __webpack_require__(4); - var utils = __webpack_require__(3); - var bidfactory = __webpack_require__(7); - var bidmanager = __webpack_require__(5); - var adloader = __webpack_require__(8); - - /** - * 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; - } - if (bid.params.refererURL) { - opts.refererURL = bid.params.refererURL; - } - if (bid.params.jstag_url) { - scriptUrl = bid.params.jstag_url; - } - if (bid.params.pgid) { - opts.pgid = bid.params.pgid; - } - } - _requestBids(); - } - - function _requestBids() { - - if (scriptUrl) { - adloader.loadScript(scriptUrl, function() { - var i; - var POX = OX(); - - POX.setPageURL(opts.pageURL); - POX.setRefererURL(opts.refererURL); - POX.addPage(opts.pgid); - - // Add each ad unit ID - for (i = 0; i < bids.length; i++) { - POX.addAdUnit(bids[i].params.unit); - } - - 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'); - // 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(); - }); - } - } - - return { - callBids: _callBids - }; - }; - - module.exports = OpenxAdapter; - - -/***/ }, -/* 13 */ -/***/ function(module, exports, __webpack_require__) { - - var CONSTANTS = __webpack_require__(4); - var utils = __webpack_require__(3); - var bidfactory = __webpack_require__(7); - var bidmanager = __webpack_require__(5); - var adloader = __webpack_require__(8); - - /** - * Adapter for requesting bids from Pubmatic. - * - * @returns {{callBids: _callBids}} - * @constructor - */ - var PubmaticAdapter = function PubmaticAdapter() { - - var bids; - var _pm_pub_id; - var _pm_optimize_adslots = []; - - function _callBids(params) { - bids = params.bids; - for (var i = 0; i < bids.length; i++) { - var bid = bids[i]; - bidmanager.pbCallbackMap['' + bid.params.adSlot] = bid; - _pm_pub_id = _pm_pub_id || bid.params.publisherId; - _pm_optimize_adslots.push(bid.params.adSlot); - } - - // Load pubmatic script in an iframe, because they call document.write - _getBids(); - } - - function _getBids() { - - // required variables for pubmatic pre-bid call - window.pm_pub_id = _pm_pub_id; - window.pm_optimize_adslots = _pm_optimize_adslots; - - //create the iframe - var iframe = utils.createInvisibleIframe(); - var elToAppend = document.getElementsByTagName('head')[0]; - //insert the iframe into document - elToAppend.insertBefore(iframe, elToAppend.firstChild); - //todo make this more browser friendly - var iframeDoc = iframe.contentWindow.document; - iframeDoc.write(_createRequestContent()); - iframeDoc.close(); - } - - function _createRequestContent() { - var content = 'inDapIF=true;'; - content += ''; - content += ''; - content += '' + - 'window.pm_pub_id = "%%PM_PUB_ID%%";' + - 'window.pm_optimize_adslots = [%%PM_OPTIMIZE_ADSLOTS%%];'; - content += ''; - - var map = {}; - map['PM_PUB_ID'] = _pm_pub_id; - map['PM_OPTIMIZE_ADSLOTS'] = _pm_optimize_adslots.map(function(adSlot) { - return "'" + adSlot + "'"; - }).join(','); - - content += ''; - content += ''; - content += 'window.parent.pbjs.handlePubmaticCallback({progKeyValueMap: progKeyValueMap, bidDetailsMap: bidDetailsMap})'; - content += ''; - content += ''; - content = utils.replaceTokenInString(content, map, '%%'); - - return content; - } - - pbjs.handlePubmaticCallback = function(response) { - var i; - var adUnit; - var adUnitInfo; - var bid; - var bidResponseMap = (response && response.bidDetailsMap) || {}; - var bidInfoMap = (response && response.progKeyValueMap) || {}; - var dimensions; - - for (i = 0; i < bids.length; i++) { - var adResponse; - bid = bids[i].params; - - adUnit = bidResponseMap[bid.adSlot] || {}; - - // adUnitInfo example: bidstatus=0;bid=0.0000;bidid=39620189@320x50;wdeal= - adUnitInfo = (bidInfoMap[bid.adSlot] || '').split(';').reduce(function(result, pair) { - var parts = pair.split('='); - result[parts[0]] = parts[1]; - return result; - }, {}); - - if (adUnitInfo.bidstatus === '1') { - dimensions = adUnitInfo.bidid.split('@')[1].split('x'); - adResponse = bidfactory.createBid(1); - adResponse.bidderCode = 'pubmatic'; - adResponse.adSlot = bid.adSlot; - adResponse.cpm = Number(adUnitInfo.bid); - adResponse.ad = unescape(adUnit.creative_tag); - adResponse.adUrl = unescape(adUnit.tracking_url); - adResponse.width = dimensions[0]; - adResponse.height = dimensions[1]; - adResponse.dealId = adUnitInfo.wdeal; - - bidmanager.addBidResponse(bids[i].placementCode, adResponse); - } else { - // Indicate an ad was not returned - adResponse = bidfactory.createBid(2); - adResponse.bidderCode = 'pubmatic'; - bidmanager.addBidResponse(bids[i].placementCode, adResponse); - } - } - }; - - return { - callBids: _callBids - }; - - }; - - module.exports = PubmaticAdapter; - -/***/ }, -/* 14 */ -/***/ function(module, exports, __webpack_require__) { - - /** - * @overview Yieldbot sponsored Prebid.js adapter. - * @author elljoh - */ - var adloader = __webpack_require__(8); - var bidfactory = __webpack_require__(7); - var bidmanager = __webpack_require__(5); - var utils = __webpack_require__(3); - - /** - * Adapter for requesting bids from Yieldbot. - * - * @returns {Object} Object containing implementation for invocation in {@link module:adaptermanger.callBids} - * @class - */ - var YieldbotAdapter = function YieldbotAdapter() { - - window.ybotq = window.ybotq || []; - - var ybotlib = { - BID_STATUS: { - PENDING: 0, - AVAILABLE: 1, - EMPTY: 2 - }, - definedSlots: [], - pageLevelOption: false, - /** - * Builds the Yieldbot creative tag. - * - * @param {String} slot - The slot name to bid for - * @param {String} size - The dimenstions of the slot - * @private - */ - buildCreative: function(slot, size) { - return '' + - ''; - }, - /** - * Bid response builder. - * - * @param {Object} slotCriteria - Yieldbot bid criteria - * @private - */ - buildBid: function(slotCriteria) { - var bid = {}; - - if (slotCriteria && slotCriteria.ybot_ad && slotCriteria.ybot_ad !== 'n') { - - bid = bidfactory.createBid(ybotlib.BID_STATUS.AVAILABLE); - - bid.cpm = parseInt(slotCriteria.ybot_cpm) / 100.0 || 0; // Yieldbot CPM bids are in cents - - var szArr = slotCriteria.ybot_size ? slotCriteria.ybot_size.split('x') : [0,0], - slot = slotCriteria.ybot_slot || '', - sizeStr = slotCriteria.ybot_size || ''; // Creative template needs the dimensions string - - bid.width = szArr[0] || 0; - bid.height = szArr[1] || 0; - - bid.ad = ybotlib.buildCreative(slot, sizeStr); - - // Add Yieldbot parameters to allow publisher bidderSettings.yieldbot specific targeting - for (var k in slotCriteria) { - bid[k] = slotCriteria[k]; - } - - } else { - bid = bidfactory.createBid(ybotlib.BID_STATUS.EMPTY); - } - - bid.bidderCode = 'yieldbot'; - return bid; - }, - /** - * Yieldbot implementation of {@link module:adaptermanger.callBids} - * @param {Object} params - Adapter bid configuration object - * @private - */ - callBids: function(params) { - - var bids = params.bids || [], - ybotq = window.ybotq || []; - - ybotlib.pageLevelOption = false; - - ybotq.push(function () { - var yieldbot = window.yieldbot; - - utils._each(bids, function(v) { - var bid = v, - psn = bid.params && bid.params.psn || 'ERROR_DEFINE_YB_PSN', - slot = bid.params && bid.params.slot || 'ERROR_DEFINE_YB_SLOT'; - - yieldbot.pub(psn); - yieldbot.defineSlot(slot, {sizes: bid.sizes || []}); - - var cbId = utils.getUniqueIdentifierStr(); - bidmanager.pbCallbackMap[cbId] = bid; - ybotlib.definedSlots.push(cbId); - }); - - yieldbot.enableAsync(); - yieldbot.go(); - }); - - ybotq.push(function () { - ybotlib.handleUpdateState(); - }); - - adloader.loadScript('//cdn.yldbt.com/js/yieldbot.intent.js'); - }, - /** - * Yieldbot bid request callback handler. - * - * @see {@link YieldbotAdapter~_callBids} - * @private - */ - handleUpdateState: function() { - var yieldbot = window.yieldbot; - - utils._each(ybotlib.definedSlots, function(v) { - var slot, - criteria, - placementCode, - adapterConfig; - - adapterConfig = bidmanager.getPlacementIdByCBIdentifer(v) || {}; - slot = adapterConfig.params.slot || ''; - criteria = yieldbot.getSlotCriteria(slot); - - placementCode = adapterConfig.placementCode || 'ERROR_YB_NO_PLACEMENT'; - var bid = ybotlib.buildBid(criteria); - - bidmanager.addBidResponse(placementCode, bid); - - }); - } - } - return { - callBids: ybotlib.callBids - }; - }; - - module.exports = YieldbotAdapter; - - -/***/ }, -/* 15 */ -/***/ function(module, exports, __webpack_require__) { - - //Factory for creating the bidderAdaptor - var CONSTANTS = __webpack_require__(4); - var utils = __webpack_require__(3); - var bidfactory = __webpack_require__(7); - var bidmanager = __webpack_require__(5); - var adloader = __webpack_require__(8); - - var ADAPTER_NAME = 'INDEXEXCHANGE'; - var ADAPTER_CODE = 'indexExchange'; - - var cygnus_index_primary_request = true; - var cygnus_index_parse_res = function() {}; - window.cygnus_index_args = {}; - - var cygnus_index_adunits = [[728,90],[120,600],[300,250],[160,600],[336,280],[234,60],[300,600],[300,50],[320,50],[970,250],[300,1050],[970,90],[180,150]]; - - var cygnus_index_start = function() { - cygnus_index_args.parseFn = cygnus_index_parse_res; - var escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; - var meta = { - '\b': '\\b', - '\t': '\\t', - '\n': '\\n', - '\f': '\\f', - '\r': '\\r', - '"': '\\"', - '\\': '\\\\' - }; - - function escapeCharacter(character) { - var escaped = meta[character]; - if (typeof escaped === 'string') { - return escaped; - } else { - return '\\u' + ('0000' + character.charCodeAt(0).toString(16)).slice(-4); - } - } - - function quote(string) { - escapable.lastIndex = 0; - if (escapable.test(string)) { - return string.replace(escapable, escapeCharacter); - } else { - return string; - } - } - - function OpenRTBRequest(siteID, parseFn, timeoutDelay) { - this.initialized = false; - if (typeof siteID !== "number" || siteID % 1 !== 0 || siteID < 0) { - throw "Invalid Site ID"; - } - if (typeof timeoutDelay === "number" && timeoutDelay % 1 === 0 && timeoutDelay >= 0) { - this.timeoutDelay = timeoutDelay; - } - - this.siteID = siteID; - this.impressions = []; - this._parseFnName = undefined; - if (top === self) { - this.sitePage = location.href; - this.topframe = 1; - } else { - this.sitePage = document.referrer; - this.topframe = 0; - } - if (typeof parseFn !== 'undefined') { - if (typeof parseFn === 'function') { - this._parseFnName = "cygnus_index_args.parseFn"; - } else { - throw "Invalid jsonp target function"; - } - } - if (typeof _IndexRequestData.requestCounter === 'undefined') { - _IndexRequestData.requestCounter = Math.floor(Math.random() * 256); - } else { - _IndexRequestData.requestCounter = (_IndexRequestData.requestCounter + 1) % 256; - } - this.requestID = String((new Date().getTime() % 2592000) * 256 + _IndexRequestData.requestCounter + 256); - this.initialized = true; - } - OpenRTBRequest.prototype.serialize = function() { - var json = '{"id":' + this.requestID + ',"site":{"page":"' + quote(this.sitePage) + '"'; - if (typeof document.referrer === 'string') { - json += ',"ref":"' + quote(document.referrer) + '"'; - } - json += '},"imp":['; - for (var i = 0; i < this.impressions.length; i++) { - var impObj = this.impressions[i]; - var ext = []; - json += '{"id":"' + impObj.id + '", "banner":{"w":' + impObj.w + ',"h":' + impObj.h + ',"topframe":' + String(this.topframe) + "}"; - if (typeof impObj.bidfloor === 'number') { - json += ',"bidfloor":' + impObj.bidfloor; - if (typeof impObj.bidfloorcur === 'string') { - json += ',"bidfloorcur":"' + quote(impObj.bidfloorcur) + '"'; - } - } - if (typeof impObj.slotID === 'string' && (!impObj.slotID.match(/^\s*$/))) { - ext.push('"sid":"' + quote(impObj.slotID) + '"'); - } - if (typeof impObj.siteID === 'number') { - ext.push('"siteID":' + impObj.siteID); - } - if (ext.length > 0) { - json += ',"ext": {' + ext.join() + '}'; - } - if (i + 1 == this.impressions.length) { - json += '}'; - } else { - json += '},'; - } - } - json += "]}"; - return json; - }; - OpenRTBRequest.prototype.setPageOverride = function(sitePageOverride) { - if (typeof sitePageOverride === 'string' && (!sitePageOverride.match(/^\s*$/))) { - this.sitePage = sitePageOverride; - return true; - } else { - return false; - } - }; - OpenRTBRequest.prototype.addImpression = function(width, height, bidFloor, bidFloorCurrency, slotID, siteID) { - var impObj = { - 'id': String(this.impressions.length + 1) - }; - if (typeof width !== 'number' || width <= 1) { - return null; - } - if (typeof height !== 'number' || height <= 1) { - return null; - } - if ((typeof slotID === 'string' || typeof slotID === 'number') && String(slotID).length <= 50) { - impObj.slotID = String(slotID); - } - impObj.w = width; - impObj.h = height; - if (bidFloor !== undefined && typeof bidFloor !== 'number') { - return null; - } - if (typeof bidFloor === 'number') { - if (bidFloor < 0) { - return null; - } - impObj.bidfloor = bidFloor; - if (bidFloorCurrency !== undefined && typeof bidFloorCurrency !== 'string') { - return null; - } - impObj.bidfloorcur = bidFloorCurrency; - } - if (typeof siteID !== 'undefined') { - if (typeof siteID === 'number' && siteID % 1 === 0 && siteID >= 0) { - impObj.siteID = siteID; - } else { - return null; - } - } - this.impressions.push(impObj); - return impObj.id; - }; - - OpenRTBRequest.prototype.buildRequest = function() { - if (this.impressions.length === 0 || this.initialized !== true) { - return; - } - var jsonURI = encodeURIComponent(this.serialize()); - var scriptSrc = window.location.protocol === 'https:' ? 'https://as-sec.casalemedia.com' : 'http://as.casalemedia.com'; - scriptSrc += '/headertag?v=9&x3=1&fn=cygnus_index_parse_res&s=' + this.siteID + '&r=' + jsonURI; - if (typeof this.timeoutDelay === "number" && this.timeoutDelay % 1 === 0 && this.timeoutDelay >= 0) { - scriptSrc += '&t=' + this.timeoutDelay; - } - return scriptSrc; - }; - try { - if (typeof cygnus_index_args === 'undefined' || typeof cygnus_index_args.siteID === 'undefined' || typeof cygnus_index_args.slots === 'undefined') { - return; - } - if (typeof _IndexRequestData === 'undefined') { - _IndexRequestData = {}; - _IndexRequestData.impIDToSlotID = {}; - _IndexRequestData.reqOptions = {}; - } - var req = new OpenRTBRequest(cygnus_index_args.siteID, cygnus_index_args.parseFn, cygnus_index_args.timeout); - if (cygnus_index_args.url && typeof cygnus_index_args.url === 'string') { - req.setPageOverride(cygnus_index_args.url); - } - _IndexRequestData.impIDToSlotID[req.requestID] = {}; - _IndexRequestData.reqOptions[req.requestID] = {}; - var slotDef, impID; - - for (var i = 0; i < cygnus_index_args.slots.length; i++) { - slotDef = cygnus_index_args.slots[i]; - - impID = req.addImpression(slotDef.width, slotDef.height, slotDef.bidfloor, slotDef.bidfloorcur, slotDef.id, slotDef.siteID); - if (impID) { - _IndexRequestData.impIDToSlotID[req.requestID][impID] = String(slotDef.id); - } - } - if (typeof cygnus_index_args.targetMode === 'number') { - _IndexRequestData.reqOptions[req.requestID].targetMode = cygnus_index_args.targetMode; - } - if (typeof cygnus_index_args.callback === 'function') { - _IndexRequestData.reqOptions[req.requestID].callback = cygnus_index_args.callback; - } - return req.buildRequest(); - } catch (e) {} - }; - - var IndexExchangeAdapter = function IndexExchangeAdapter() { - var slotIdMap = {}; - var requiredParams = [ - /* 0 */ - 'id', - /* 1 */ - 'siteID' - ]; - var firstAdUnitCode = ''; - - function _callBids(request) { - var bidArr = request.bids; - - if (!utils.hasValidBidRequest(bidArr[0].params, requiredParams, ADAPTER_NAME)) { - return; - } - - cygnus_index_args.slots = []; - var bidCount = 0; - - //Grab the slot level data for cygnus_index_args - for (i = 0; i < bidArr.length; i++) { - var bid = bidArr[i]; - - var width; - var height; - - outer: for (var j = 0; j < bid.sizes.length; j++) { - inner: for (var k = 0; k < cygnus_index_adunits.length; k++) { - if (bid.sizes[j][0] === cygnus_index_adunits[k][0] && - bid.sizes[j][1] === cygnus_index_adunits[k][1]) { - width = bid.sizes[j][0]; - height = bid.sizes[j][1]; - break outer; - } - } - } - - if (bid.params.timeout && typeof cygnus_index_args.timeout === 'undefined') { - cygnus_index_args.timeout = bid.params.timeout; - } - - if (bid.params.siteID && typeof cygnus_index_args.siteID === 'undefined') { - cygnus_index_args.siteID = bid.params.siteID; - } - - if (bid.params.sqps && typeof cygnus_index_args.SQPS === 'undefined') { - cygnus_index_args.slots.push({ - id:"SPQS", - width: bid.params.sqps.width, - height: bid.params.sqps.height, - siteID: bid.params.sqps.siteID || cygnus_index_args.siteID - }); - } - - if (utils.hasValidBidRequest(bid.params, requiredParams, ADAPTER_NAME)) { - firstAdUnitCode = bid.placementCode; - var slotId = bid.params[requiredParams[0]]; - slotIdMap[slotId] = bid; - - if (cygnus_index_primary_request) { - cygnus_index_args.slots.push({ - id: bid.params.id, - width: width, - height: height, - siteID: bid.params.siteID || cygnus_index_args.siteID - }); - - bidCount++; - - if (bid.params.tier2SiteID) { - cygnus_index_args.slots.push({ - id: "T1_"+bid.params.id, - width: width, - height: height, - siteID: bid.params.tier2SiteID - }); - } - if (bid.params.tier3SiteID) { - cygnus_index_args.slots.push({ - id:"T2_"+bid.params.id, - width:width, - height:height, - siteID:bid.params.tier3SiteID - }); - } - } - } - bidmanager.setExpectedBidsCount(ADAPTER_CODE, bidCount); - } - cygnus_index_primary_request = false; - - adloader.loadScript(cygnus_index_start()); - - window.cygnus_index_ready_state = function() { - try { - var indexObj = _IndexRequestData.targetIDToBid; - var lookupObj = cygnus_index_args; - - if (utils.isEmpty(indexObj)) { - var bid = bidfactory.createBid(2); - bid.bidderCode = ADAPTER_CODE; - logErrorBidResponse(); - return; - } - - utils._each(indexObj, function(adContents, cpmAndSlotId) { - utils._each(slotIdMap, function(bid, adSlotId) { - var obj = cpmAndSlotId.split('_'); - var currentId = obj[0]; - var currentCPM = obj[1]; - if (currentId === adSlotId) { - var bidObj = slotIdMap[adSlotId]; - var adUnitCode = bidObj.placementCode; - var slotObj = getSlotObj(cygnus_index_args, adSlotId); - - bid = bidfactory.createBid(1); - bid.cpm = currentCPM / 100; - bid.ad = adContents[0]; - bid.ad_id = adSlotId; - bid.bidderCode = ADAPTER_CODE; - bid.width = slotObj.width; - bid.height = slotObj.height; - bid.siteID = slotObj.siteID; - - bidmanager.addBidResponse(adUnitCode, bid); - } - }); - }); - } catch (e) { - utils.logError('Error calling index adapter', ADAPTER_NAME, e); - logErrorBidResponse(); - } - }; - } - - function getSlotObj(obj, id) { - var arr = obj.slots; - var returnObj = {}; - utils._each(arr, function(value) { - if (value.id === id) { - returnObj = value; - } - }); - return returnObj; - } - - function logErrorBidResponse() { - //no bid response - var bid = bidfactory.createBid(2); - bid.bidderCode = ADAPTER_CODE; - //log error to first add unit - bidmanager.addBidResponse(firstAdUnitCode, bid); - } - - return { - callBids: _callBids - }; - //end of Rubicon bid adaptor - }; - - module.exports = IndexExchangeAdapter; - - -/***/ }, -/* 16 */ -/***/ function(module, exports, __webpack_require__) { - - var CONSTANTS = __webpack_require__(4); - var utils = __webpack_require__(3); - var bidfactory = __webpack_require__(7); - var bidmanager = __webpack_require__(5); - var adloader = __webpack_require__(8); - - var allPlacementCodes; - - /** - * Adapter for requesting bids from Sovrn - */ - var SovrnAdapter = function SovrnAdapter() { - var sovrnUrl = 'ap.lijit.com/rtb/bid'; - - function _callBids(params) { - var sovrnBids = params.bids || []; - // De-dupe by tagid then issue single bid request for all bids - _requestBids(_getUniqueTagids(sovrnBids)); - } - - // filter bids to de-dupe them? - function _getUniqueTagids(bids) { - var key; - var map = {}; - var Tagids = []; - bids.forEach(function(bid) { - map[utils.getBidIdParamater('tagid', bid.params)] = bid; - }); - for (key in map) { - if (map.hasOwnProperty(key)) { - Tagids.push(map[key]); - } - } - return Tagids; - } - - function _requestBids(bidReqs) { - // build bid request object - var domain = window.location.host; - var page = window.location.pathname + location.search + location.hash; - - var sovrnImps = []; - allPlacementCodes = []; - //build impression array for sovrn - utils._each(bidReqs, function(bid) - { - var tagId = utils.getBidIdParamater('tagid', bid.params); - var bidFloor = utils.getBidIdParamater('bidfloor', bid.params); - var adW=0,adH=0; - - //sovrn supports only one size per tagid, so we just take the first size if there are more - //if we are a 2 item array of 2 numbers, we must be a SingleSize array - var sizeArrayLength = bid.sizes.length; - if (sizeArrayLength === 2 && typeof bid.sizes[0] === 'number' && typeof bid.sizes[1] === 'number') { - adW=bid.sizes[0]; - adH=bid.sizes[1]; - } - else - { - adW=bid.sizes[0][0]; - adH=bid.sizes[0][1]; - } - var imp = - { - id: utils.getUniqueIdentifierStr(), - banner: { - w: adW, - h: adH - }, - tagid: tagId, - bidfloor: bidFloor - }; - sovrnImps.push(imp); - bidmanager.pbCallbackMap[imp.id] = bid; - allPlacementCodes.push(bid.placementCode); - }); - - // build bid request with impressions - var sovrnBidReq = { - id: utils.getUniqueIdentifierStr(), - imp: sovrnImps, - site:{ - domain: domain, - page: page - } - }; - - var scriptUrl = '//'+sovrnUrl+'?callback=window.pbjs.sovrnResponse' + - '&br=' + encodeURIComponent(JSON.stringify(sovrnBidReq)); - adloader.loadScript(scriptUrl, null); - } - - function addBlankBidResponsesForAllPlacementsExceptThese(placementsWithBidsBack){ - utils._each(allPlacementCodes, function(placementCode) - { - if(utils.contains(placementsWithBidsBack, placementCode)) { - // A bid was returned for this placement already - } else { - // Add a no-bid response for this placement. - var bid = {}; - bid = bidfactory.createBid(2); - bid.bidderCode = 'sovrn'; - bidmanager.addBidResponse(placementCode, bid); - } - }); - } - - - //expose the callback to the global object: - pbjs.sovrnResponse = function(sovrnResponseObj) { - // valid object? - if (sovrnResponseObj && sovrnResponseObj.id) { - // valid object w/ bid responses? - if (sovrnResponseObj.seatbid && sovrnResponseObj.seatbid.length !==0 && sovrnResponseObj.seatbid[0].bid && sovrnResponseObj.seatbid[0].bid.length !== 0) { - var placementsWithBidsBack = []; - sovrnResponseObj.seatbid[0].bid.forEach(function(sovrnBid){ - - var responseCPM; - var placementCode = ''; - var id = sovrnBid.impid; - var bid = {}; - - // try to fetch the bid request we sent Sovrn - var bidObj = bidmanager.getPlacementIdByCBIdentifer(id); - if (bidObj){ - placementCode = bidObj.placementCode; - placementsWithBidsBack.push(placementCode); - bidObj.status = CONSTANTS.STATUS.GOOD; - - //place ad response on bidmanager._adResponsesByBidderId - responseCPM = parseFloat(sovrnBid.price); - - if(responseCPM !== 0) { - sovrnBid.placementCode = placementCode; - sovrnBid.size = bidObj.sizes; - var responseAd = sovrnBid.adm; - - // build impression url from response - var responseNurl = ''; - - //store bid response - //bid status is good (indicating 1) - bid = bidfactory.createBid(1); - bid.creative_id = sovrnBid.Id; - bid.bidderCode = 'sovrn'; - bid.cpm = responseCPM; - - //set ad content + impression url - // sovrn returns '; - bid.ad_id = rubiconAd.ad_id; - bid.bidderCode = 'rubicon'; - bid.sizeId = rubiconAd.size_id; - bid.width = width; - bid.height = height; - - }else{ - bid = bidfactory.createBid(2); - bid.bidderCode = 'rubicon'; - var bidObj = bidmanager.getPlacementIdByCBIdentifer(getBidId(response)); - if (bidObj) { - placementCode = bidObj.placementCode; - } - } - - } catch (e) { - utils.logError('Error parsing rubicon response bid: ' + e.message); - } - - } else { - //set bid response code to 2 = no response or error - bid = bidfactory.createBid(2); - bid.bidderCode = 'rubicon'; - var bidObj = bidmanager.getPlacementIdByCBIdentifer(getBidId(response)); - if (bidObj) { - placementCode = bidObj.placementCode; - } - - } - - //add the bid response here - bidmanager.addBidResponse(placementCode, bid); - - }; - - return { - callBids: callBids - - }; - //end of Rubicon bid adaptor - }; - - module.exports = RubiconAdapter; - -/***/ } -/******/ ]); \ No newline at end of file diff --git a/build/dist/prebid.js b/build/dist/prebid.js deleted file mode 100644 index 6267c32de8b..00000000000 --- a/build/dist/prebid.js +++ /dev/null @@ -1,2 +0,0 @@ -!function(e){function t(i){if(r[i])return r[i].exports;var n=r[i]={exports:{},id:i,loaded:!1};return e[i].call(n.exports,n,n.exports,t),n.loaded=!0,n.exports}var r={};return t.m=e,t.c=r,t.p="",t(0)}([function(e,t,r){r(1),r(8),r(7),r(5),r(6),r(20),r(21),r(3),r(10),r(19),r(11),r(9),r(15),r(12),r(13),r(17),r(2),r(22),r(16),r(18),e.exports=r(14)},function(e,t,r){var i=r(2),n=r(9),a=r(11),d=r(12),o=r(13),s=r(14),c=r(15),u=r(16),p=r(17),l=r(18),f=r(19),g=r(5),m=r(3),h=r(4),b=r(6),v={};t.bidderRegistry=v,t.callBids=function(e){for(var t=0;t;(function (w, fe) { w.rubicontag.renderCreative(fe, "',_='"); }(window.top, (document.body || document.documentElement)));';window.rubicontag=window.rubicontag||{},window.rubicontag.cmd=window.rubicontag.cmd||[];var w=null;return{callBids:g}};e.exports=o},function(e,t,r){function i(){return v()+Math.random().toString(16).substr(2)}function n(){return window.console&&window.console.log}{var a=r(4),d="object",o="string",s="number",c=!1,u=5,p=20,l=20,f="Array",g="String",m="Function",h=Object.prototype.toString,b=Object.prototype.hasOwnProperty;Array.prototype.slice}t.replaceTokenInString=function(e,t,r){return this._each(t,function(t,i){t=void 0===t?"":t;var n=r+i.toUpperCase()+r,a=new RegExp(n,"g");e=e.replace(a,t)}),e};var v=function(){var e=0;return function(){return e++,e}}();t.getUniqueIdentifierStr=i,t.getBidIdParamater=function(e,t){return t&&t[e]?t[e]:""},t.tryAppendQueryString=function(e,t,r){return r?e+=t+"="+encodeURIComponent(r)+"&":e},t.parseQueryStringParameters=function(e){var t="";for(var r in e)e.hasOwnProperty(r)&&(t+=r+"="+encodeURIComponent(e[r])+"&");return t},t.transformAdServerTargetingObj=function(e){var t="";if(!e)return"";for(var r in e)e.hasOwnProperty(r)&&(t+=r+"="+encodeURIComponent(e[r])+"&");return t},t.extend=function(e,t){return e=e||{},this._each(t,function(r,i){e[i]=typeof t[i]===d?this.extend(e[i],t[i]):t[i]}),e},t.parseSizesInput=function(e){var t=[];if(typeof e===o){var r=e.split(","),i=/^(\d)+x(\d)+$/i;if(r)for(var n in r)B(r,n)&&r[n].match(i)&&t.push(r[n])}else if(typeof e===d){var a=e.length;if(a>0)if(2===a&&typeof e[0]===s&&typeof e[1]===s)t.push(this.parseGPTSingleSizeArray(e));else for(var c=0;a>c;c++)t.push(this.parseGPTSingleSizeArray(e[c]))}return t},t.parseGPTSingleSizeArray=function(e){return!this.isArray(e)||2!==e.length||isNaN(e[0])||isNaN(e[1])?void 0:e[0]+"x"+e[1]},t.getTopWindowUrl=function(){try{return window.top.location.href}catch(e){return window.location.href}},t.logMessage=function(e){_()&&n()&&console.log("MESSAGE: "+e)},t.hasConsoleLogger=n;var y=function(e){return e?window.console.error?"error":"log":""}(n()),_=function(){return pbjs.logging===!1&&c===!1&&(pbjs.logging="TRUE"===w(a.DEBUG_MODE).toUpperCase(),c=!0),pbjs.logging?!0:!1};t.debugTurnedOn=_,t.logError=function(e,t,r){var i=t||"ERROR";_()&&n()&&console[y].call(console,i+": "+e,r||"")},t.createInvisibleIframe=function(){var e=document.createElement("iframe");return e.id=i(),e.height=0,e.width=0,e.border="0px",e.hspace="0",e.vspace="0",e.marginWidth="0",e.marginHeight="0",e.style.border="0",e.scrolling="no",e.frameBorder="0",e.src="about:self",e.style="display:none",e};var w=function(e){var t="[\\?&]"+e+"=([^&#]*)",r=new RegExp(t),i=r.exec(window.location.search);return null===i?"":decodeURIComponent(i[1].replace(/\+/g," "))};t.getPriceBucketString=function(e){var t="",r="",i="",n=0,a={low:t,med:r,high:i};try{n=parseFloat(e),n&&(a.low=n>u?u.toFixed(2):(Math.floor(2*e)/2).toFixed(2),a.med=n>p?p.toFixed(2):(Math.floor(10*e)/10).toFixed(2),a.high=n>l?l.toFixed(2):(Math.floor(100*e)/100).toFixed(2))}catch(d){this.logError("Exception parsing CPM :"+d.message)}return a},t.hasValidBidRequest=function(e,t,r){for(var i=0;i0);for(var t in e)if(b.call(e,t))return!1;return!0},t._each=function(e,t){if(!this.isEmpty(e)){if(this.isFn(e.forEach))return e.forEach(t,this);var r=0,i=e.length;if(i>0)for(;i>r;r++)t(e[r],r,e);else for(r in e)b.call(e,r)&&t.call(this,e[r],r)}},t.contains=function(e,t){if(this.isEmpty(e))return!1;if(this.isFn(e.indexOf))return-1!==e.indexOf(t);for(var r=e.length;r--;)if(e[r]===t)return!0;return!1},t._map=function(e,t){if(this.isEmpty(e))return[];if(this.isFn(e.map))return e.map(t);var r=[];return this._each(e,function(i,n){r.push(t(i,n,e))}),r};var B=function(e,t){return e.hasOwnProperty?e.hasOwnProperty(t):typeof e[t]!==UNDEFINED&&e.constructor.prototype[t]!==e[t]}},function(e){e.exports={JSON_MAPPING:{PL_CODE:"code",PL_SIZE:"sizes",PL_BIDS:"bids",BD_BIDDER:"bidder",BD_ID:"paramsd",BD_PL_ID:"placementId",ADSERVER_TARGETING:"adserverTargeting",BD_SETTING_STANDARD:"standard"},DEBUG_MODE:"pbjs_debug",STATUS:{GOOD:"good",TIMEOUT:"timed out"},CB:{TYPE:{ALL_BIDS_BACK:"allRequestedBidsBack",AD_UNIT_BIDS_BACK:"adUnitBidsBack"}},objectType_function:"function",objectType_undefined:"undefined",objectType_object:"object",objectType_string:"string",objectType_number:"number",EVENTS:{BID_ADJUSTMENT:"bidAdjustment",BID_TIMEOUT:"bidTimeout",BID_REQUESTED:"bidRequested",BID_RESPONSE:"bidResponse",BID_WON:"bidWon"}}},function(e,t,r){function i(){S={};for(var e=0;et)&&(e=!1)}),e}function f(e){var t=e.bidderCode,r=e.cpm;if(t&&pbjs.bidderSettings&&pbjs.bidderSettings[t]&&typeof pbjs.bidderSettings[t].bidCpmAdjustment===b)try{r=pbjs.bidderSettings[t].bidCpmAdjustment.call(null,e.cpm)}catch(i){m.logError("Error during bid adjustment","bidmanager.js",i)}0!==r&&(e.cpm=r)}var g=r(4),m=r(3),h=(r(1),r(6)),b="function",v="undefined",y=[],_=[],w=null,B={},I={};t.pbCallbackMap=I;var C={};t.pbBidResponseByPlacement=C;var E={};t._adResponsesByBidderId=E;var S={};t.bidResponseReceivedCount=S;var A={},T=!1,R=!1,D={},P={};t.getPlacementIdByCBIdentifer=function(e){return I[e]},t.getBidResponseByAdUnit=function(){return C},t.clearAllBidResponses=function(){T=!1,R=!1,i(),a(),_.called=!1;for(var e in this.pbBidResponseByPlacement)delete this.pbBidResponseByPlacement[e]},t.getTimedOutBidders=function(){var e=[];return m._each(S,function(t,r){0===t&&e.push(r)}),e},t.increaseBidResponseReceivedCount=function(e){n(e)},t.setExpectedBidsCount=function(e,t){A[e]=t},t.getExpectedBidsCount=d,t.addBidResponse=function(e,t){var r={};if(t){t.requestTimestamp=P[t.bidderCode],t.responseTimestamp=(new Date).getTime(),t.timeToRespond=t.responseTimestamp-t.requestTimestamp,n(t.bidderCode),2===t.getStatusCode()&&(t.cpm=0),h.emit(g.EVENTS.BID_ADJUSTMENT,t),h.emit(g.EVENTS.BID_RESPONSE,e,t);var i=m.getPriceBucketString(t.cpm,t.height,t.width);t.pbLg=i.low,t.pbMg=i.med,t.pbHg=i.high,t.adUnitCode=e,t.bidder=t.bidderCode;var a={};t.bidderCode&&0!==t.cpm&&(a=this.getKeyValueTargetingPairs(t.bidderCode,t),t.adserverTargeting=a),t.adId&&(E[t.adId]=t),e&&C[e]?(r=C[e],r.bids.push(t),r.bidsReceivedCount++):m.logError("Internal error in bidmanager.addBidResponse. Params: "+e+" & "+t)}else r=this.createEmptyBidResponseObj();C[e]=r,this.checkIfAllBidsAreIn(e)},t.createEmptyBidResponseObj=function(){return{bids:[],allBidsAvailable:!1,bidsReceivedCount:0}},t.getKeyValueTargetingPairs=function(e,t){var r={},i=pbjs.bidderSettings||{};return e&&t&&i&&i[e]&&i[e][g.JSON_MAPPING.ADSERVER_TARGETING]?(o(r,i[e],t),t.alwaysUseBid=i[e].alwaysUseBid):D[e]?(o(r,D[e],t),t.alwaysUseBid=D[e].alwaysUseBid):t&&i&&(i[g.JSON_MAPPING.BD_SETTING_STANDARD]||(i[g.JSON_MAPPING.BD_SETTING_STANDARD]={adserverTargeting:[{key:"hb_bidder",val:function(e){return e.bidderCode}},{key:"hb_adid",val:function(e){return e.adId}},{key:"hb_pb",val:function(e){return e.pbMg}},{key:"hb_size",val:function(e){return e.size}}]}),o(r,i[g.JSON_MAPPING.BD_SETTING_STANDARD],t)),r},t.registerDefaultBidderSetting=function(e,t){D[e]=t},t.registerBidRequestTime=function(e,t){P[e]=t},t.executeCallback=function(){if(typeof pbjs.registerBidCallbackHandler===b&&!R)try{pbjs.registerBidCallbackHandler(),R=!0}catch(e){R=!0,m.logError("Exception trying to execute callback handler registered : "+e.message)}if(_.called!==!0){var t=[];c(_,t),_.called=!0}if(w){var t=[],r=pbjs.getBidResponses();t.push(r),c(w,t),w=null}},t.allBidsBack=function(){return T},t.setBidderMap=function(e){B=e},t.checkIfAllBidsAreIn=function(e){T=l(),p(e),T&&this.executeCallback()},t.addOneTimeCallback=function(e){w=e},t.addCallback=function(e,t,r){t.id=e,g.CB.TYPE.ALL_BIDS_BACK===r?_.push(t):g.CB.TYPE.AD_UNIT_BIDS_BACK===r&&y.push(t)},h.on(g.EVENTS.BID_ADJUSTMENT,function(e){f(e)})},function(e,t,r){var i=r(3),n=r(4),a=Array.prototype.slice,d=i._map(n.EVENTS,function(e){return e}),o=[];e.exports=function(){function e(e,t){i.logMessage("Emitting event for: "+e),o.push({eventType:e,args:t}),i._each(r[e],function(e){if(e)try{e.apply(null,t)}catch(r){i.logError("Error executing handler:","events.js",r)}})}function t(e){return i.contains(d,e)}var r={},n={};return n.on=function(e,n){t(e)?(r[e]=r[e]||[],r[e].push(n)):i.logError("Wrong event name : "+e+" Valid event names :"+d)},n.emit=function(t){var r=a.call(arguments,1);e(t,r)},n.off=function(e,t,n){i.isEmpty(r[e])||i._each(r[e],function(e){null!==e[t]&&void 0!==e[t]&&("undefined"==typeof n||e[t]===n)&&(e[t]=null)})},n.get=function(){return r},n.getEvents=function(){var e=[];return i._each(o,function(t){var r=i.extend({},t);e.push(r)}),e},n}()},function(e,t,r){function i(e){function t(){switch(i){case 0:return"Pending";case 1:return"Bid available";case 2:return"Bid returned empty or error response";case 3:return"Bid timed out"}}var r=n.getUniqueIdentifierStr(),i=e||0;this.bidderCode="",this.width=0,this.height=0,this.statusMessage=t(),this.adId=r,this.getStatusCode=function(){return i},this.getSize=function(){return this.width+"x"+this.height}}var n=r(3);t.createBid=function(e){return new i(e)}},function(e,t,r){var i=r(3);t.loadScript=function(e,t){if(!e)return i.logError("Error attempting to request empty URL","adloader.js:loadScript"),void 0;var r=document.createElement("script");r.type="text/javascript",r.async=!0,t&&"function"==typeof t&&(r.readyState?r.onreadystatechange=function(){("loaded"==r.readyState||"complete"==r.readyState)&&(r.onreadystatechange=null,t())}:r.onload=function(){t()}),r.src=e;var n=document.getElementsByTagName("head");n=n.length?n:document.getElementsByTagName("body"),n.length&&(n=n[0],n.insertBefore(r,n.firstChild))},t.trackPixel=function(e){var t,r;return e&&"string"==typeof e?(t=e.indexOf("?")>0?"&":"?",r=e+t+"rnd="+Math.floor(1e7*Math.random()),(new Image).src=r,r):(i.logMessage("Missing or invalid pixelUrl."),void 0)}},function(e,t,r){var i=r(4),n=r(3),a=r(8),d=r(5),o=r(7),s=r(10),c=function(){function e(e,t){var r=n.getBidIdParamater("placementId",e.params),i=n.getBidIdParamater("memberId",e.params),a=n.getBidIdParamater("member",e.params),d=n.getBidIdParamater("invCode",e.params),o=n.getBidIdParamater("query",e.params),s=n.getBidIdParamater("referrer",e.params),c=n.getBidIdParamater("alt_referrer",e.params),u="http"+("https:"===document.location.protocol?"s://secure.adnxs.com/jpt?":"://ib.adnxs.com/jpt?");u=n.tryAppendQueryString(u,"callback","pbjs.handleAnCB"),u=n.tryAppendQueryString(u,"callback_uid",t),u=n.tryAppendQueryString(u,"psa","0"),u=n.tryAppendQueryString(u,"id",r),a?u=n.tryAppendQueryString(u,"member_id",a):i&&(u=n.tryAppendQueryString(u,"member_id",i),n.logMessage('appnexus.callBids: "memberId" will be deprecated soon. Please use "member" instead')),u=n.tryAppendQueryString(u,"code",d),u=n.tryAppendQueryString(u,"code",d);var p="",l=n.parseSizesInput(e.sizes),f=l.length;if(f>0&&(p="size="+l[0],f>1)){p+="&promo_sizes=";for(var g=1;f>g;g++)p+=l[g]+=",";p&&","===p.charAt(p.length-1)&&(p=p.slice(0,p.length-1))}p&&(u+=p+"&");var m=n.parseQueryStringParameters(o);m&&(u+=m);var h=n.extend({},e.params);delete h.placementId,delete h.memberId,delete h.invCode,delete h.query,delete h.referrer,delete h.alt_referrer,delete h.member;var b=n.parseQueryStringParameters(h);return b&&(u+=b),""===s&&(s=n.getTopWindowUrl()),u=n.tryAppendQueryString(u,"referrer",s),u=n.tryAppendQueryString(u,"alt_referrer",c),u.lastIndexOf("&")===u.length-1&&(u=u.substring(0,u.length-1)),n.logMessage("jpt request built: "+u),e.startTime=(new Date).getTime(),u}var r=s.createNew("appnexus");return r.callBids=function(t){var i=r.getBidderCode(),o=t.bids,s=o.length;d.setExpectedBidsCount(i,s);for(var c=0;s>c;c++){var u=o[c],p=n.getUniqueIdentifierStr();a.loadScript(e(u,p)),d.pbCallbackMap[p]=u}},pbjs.handleAnCB=function(e){var t;if(e&&e.callback_uid){var r,a=e.callback_uid,s="",c=d.getPlacementIdByCBIdentifer(a);c&&(t=c.bidder,s=c.placementCode,c.status=i.STATUS.GOOD),n.logMessage("JSONP callback function called for ad ID: "+a);var u=[];if(e.result&&e.result.cpm&&0!==e.result.cpm){r=parseInt(e.result.cpm,10),r/=1e4;var p=(e.result.ad,e.result.creative_id);u=o.createBid(1),u.creative_id=p,u.bidderCode=t,u.cpm=r,u.adUrl=e.result.ad,u.width=e.result.width,u.height=e.result.height,u.dealId=e.result.deal_id,d.addBidResponse(s,u)}else n.logMessage("No prebid response from AppNexus for placement code "+s),u=o.createBid(2),u.bidderCode=t,d.addBidResponse(s,u)}else n.logMessage("No prebid response for placement %%PLACEMENT%%")},{callBids:r.callBids,setBidderCode:r.setBidderCode,createNew:t.createNew,buildJPTCall:e}};t.createNew=function(){return new c}},function(e,t){function r(e){function t(e){n=e}function r(){return n}function i(){}var n=e;return{callBids:i,setBidderCode:t,getBidderCode:r}}t.createNew=function(e){return new r(e)}},function(e,t,r){var i=r(3),n=r(7),a=r(5),d=r(8),o=function(){function e(e){return(e||"alias").replace(l,"")+ ++y}function t(e){var t=b.createElement("DIV");return e&&e.length||(e="ad-placeholder-"+ ++_),t.id=e+"-head-unit",v.appendChild(t),t.id}function r(e,t){var r,d=h[t.placement];if(!d)return i.logError("mismatched bid: "+t.placement,g,t),void 0;if(r=e.getCPM(),null===r||isNaN(r))return o(e,t);var s=n.createBid(1);s.bidderCode=g,s.ad=e.getCreative()+e.getPixels(),s.cpm=r,s.width=e.getAdWidth(),s.height=e.getAdHeight(),s.creativeId=e.getCreativeId(),a.addBidResponse(d.placementCode,s)}function o(e,t){var r=h[t.alias||t.placement];if(!r)return i.logError("mismatched bid: "+t.placement,g,t),void 0;var d=n.createBid(2);d.bidderCode=g,d.reason=e.getNbr(),d.raw=e.getResponse(),a.addBidResponse(r.placementCode,d)}function s(r){return h[r.params.placement]=r,{adContainerId:t(r.params.adContainerId),server:r.params.server,sizeid:r.params.sizeId||0,pageid:r.params.pageId,secure:!1,serviceType:"pubapi",performScreenDetection:!1,alias:r.params.alias||e(r.placementCode),network:r.params.network,placement:parseInt(r.params.placement),gpt:{adUnitPath:r.params.adUnitPath||r.placementCode,size:r.params.size||(r.sizes||[])[0]},params:{cors:"yes",cmd:"bid"},pubApiConfig:m,placementCode:r.placementCode}}function c(){return window.ADTECH?(i._each(p,function(e){var t=s(e);window.ADTECH.loadAd(t)}),void 0):(i.logError("window.ADTECH is not present!",g),void 0)}function u(e){p=e.bids,p&&p.length&&d.loadScript(f,c)}var p,l=/\W/g,f=window.location.protocol+"//aka-cdn.adtechus.com/dt/common/DAC.js",g="aol",m={pixelsDivId:"pixelsDiv",defaultKey:"aolBid",roundingConfig:[{from:0,to:999,roundFunction:"tenCentsRound"},{from:1e3,to:-1,roundValue:1e3}],pubApiOK:r,pubApiER:o},h={},b=window.document,v=b.getElementsByTagName("HEAD")[0],y=0,_=0;return{callBids:u}};e.exports=o},function(e,t,r){var i=(r(4),r(3),r(7)),n=r(5),a=r(8),d=function(e){function t(e){o=e.bids||[];for(var t=0;t',d.adUrl=a.get("ad_url"),d.width=a.get("width"),d.height=a.get("height"),n.addBidResponse(r.placementCode,d)}else d=i.createBid(2),d.bidderCode="openx",n.addBidResponse(r.placementCode,d)},OX.Hooks.ON_AD_RESPONSE),t.load()})}var d,o,s=e||{};return{callBids:t}};e.exports=d},function(e,t,r){var i=(r(4),r(3)),n=r(7),a=r(5),d=(r(8),function(){function e(e){d=e.bids;for(var r=0;r',e+="",e+="",e=i.replaceTokenInString(e,t,"%%")}var d,o,s=[];return pbjs.handlePubmaticCallback=function(e){var t,r,i,o,s,c=e&&e.bidDetailsMap||{},u=e&&e.progKeyValueMap||{};for(t=0;t"},buildBid:function(t){var r={};if(t&&t.ybot_ad&&"n"!==t.ybot_ad){r=n.createBid(e.BID_STATUS.AVAILABLE),r.cpm=parseInt(t.ybot_cpm)/100||0;var i=t.ybot_size?t.ybot_size.split("x"):[0,0],a=t.ybot_slot||"",d=t.ybot_size||"";r.width=i[0]||0,r.height=i[1]||0,r.ad=e.buildCreative(a,d);for(var o in t)r[o]=t[o]}else r=n.createBid(e.BID_STATUS.EMPTY);return r.bidderCode="yieldbot",r},callBids:function(t){var r=t.bids||[],n=window.ybotq||[];e.pageLevelOption=!1,n.push(function(){var t=window.yieldbot;d._each(r,function(r){var i=r,n=i.params&&i.params.psn||"ERROR_DEFINE_YB_PSN",o=i.params&&i.params.slot||"ERROR_DEFINE_YB_SLOT";t.pub(n),t.defineSlot(o,{sizes:i.sizes||[]});var s=d.getUniqueIdentifierStr();a.pbCallbackMap[s]=i,e.definedSlots.push(s)}),t.enableAsync(),t.go()}),n.push(function(){e.handleUpdateState()}),i.loadScript("//cdn.yldbt.com/js/yieldbot.intent.js")},handleUpdateState:function(){var t=window.yieldbot;d._each(e.definedSlots,function(r){var i,n,d,o;o=a.getPlacementIdByCBIdentifer(r)||{},i=o.params.slot||"",n=t.getSlotCriteria(i),d=o.placementCode||"ERROR_YB_NO_PLACEMENT";var s=e.buildBid(n);a.addBidResponse(d,s)})}};return{callBids:e.callBids}};e.exports=o},function(e,t,r){var n=(r(4),r(3)),a=r(7),d=r(5),o=r(8),s="INDEXEXCHANGE",c="indexExchange",u=!0,p=function(){};window.cygnus_index_args={};var l=[[728,90],[120,600],[300,250],[160,600],[336,280],[234,60],[300,600],[300,50],[320,50],[970,250],[300,1050],[970,90],[180,150]],f=function(){function e(e){var t=n[e];return"string"==typeof t?t:"\\u"+("0000"+e.charCodeAt(0).toString(16)).slice(-4)}function t(t){return i.lastIndex=0,i.test(t)?t.replace(i,e):t}function r(e,t,r){if(this.initialized=!1,"number"!=typeof e||e%1!==0||0>e)throw"Invalid Site ID";if("number"==typeof r&&r%1===0&&r>=0&&(this.timeoutDelay=r),this.siteID=e,this.impressions=[],this._parseFnName=void 0,top===self?(this.sitePage=location.href,this.topframe=1):(this.sitePage=document.referrer,this.topframe=0),"undefined"!=typeof t){if("function"!=typeof t)throw"Invalid jsonp target function";this._parseFnName="cygnus_index_args.parseFn"}_IndexRequestData.requestCounter="undefined"==typeof _IndexRequestData.requestCounter?Math.floor(256*Math.random()):(_IndexRequestData.requestCounter+1)%256,this.requestID=String((new Date).getTime()%2592e3*256+_IndexRequestData.requestCounter+256),this.initialized=!0}cygnus_index_args.parseFn=p;var i=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,n={"\b":"\\b"," ":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"};r.prototype.serialize=function(){var e='{"id":'+this.requestID+',"site":{"page":"'+t(this.sitePage)+'"';"string"==typeof document.referrer&&(e+=',"ref":"'+t(document.referrer)+'"'),e+='},"imp":[';for(var r=0;r0&&(e+=',"ext": {'+n.join()+"}"),e+=r+1==this.impressions.length?"}":"},"}return e+="]}"},r.prototype.setPageOverride=function(e){return"string"!=typeof e||e.match(/^\s*$/)?!1:(this.sitePage=e,!0)},r.prototype.addImpression=function(e,t,r,i,n,a){var d={id:String(this.impressions.length+1)};if("number"!=typeof e||1>=e)return null;if("number"!=typeof t||1>=t)return null;if(("string"==typeof n||"number"==typeof n)&&String(n).length<=50&&(d.slotID=String(n)),d.w=e,d.h=t,void 0!==r&&"number"!=typeof r)return null;if("number"==typeof r){if(0>r)return null;if(d.bidfloor=r,void 0!==i&&"string"!=typeof i)return null;d.bidfloorcur=i}if("undefined"!=typeof a){if(!("number"==typeof a&&a%1===0&&a>=0))return null;d.siteID=a}return this.impressions.push(d),d.id},r.prototype.buildRequest=function(){if(0!==this.impressions.length&&this.initialized===!0){var e=encodeURIComponent(this.serialize()),t="https:"===window.location.protocol?"https://as-sec.casalemedia.com":"http://as.casalemedia.com";return t+="/headertag?v=9&x3=1&fn=cygnus_index_parse_res&s="+this.siteID+"&r="+e,"number"==typeof this.timeoutDelay&&this.timeoutDelay%1===0&&this.timeoutDelay>=0&&(t+="&t="+this.timeoutDelay),t}};try{if("undefined"==typeof cygnus_index_args||"undefined"==typeof cygnus_index_args.siteID||"undefined"==typeof cygnus_index_args.slots)return;"undefined"==typeof _IndexRequestData&&(_IndexRequestData={},_IndexRequestData.impIDToSlotID={},_IndexRequestData.reqOptions={});var a=new r(cygnus_index_args.siteID,cygnus_index_args.parseFn,cygnus_index_args.timeout);cygnus_index_args.url&&"string"==typeof cygnus_index_args.url&&a.setPageOverride(cygnus_index_args.url),_IndexRequestData.impIDToSlotID[a.requestID]={},_IndexRequestData.reqOptions[a.requestID]={};for(var d,o,s=0;s';s=d.createBid(1),s.creative_id=e.Id,s.bidderCode="sovrn",s.cpm=r,s.ad=decodeURIComponent(u+p);var l=c.sizes.length;2===l&&"number"==typeof c.sizes[0]&&"number"==typeof c.sizes[1]?(s.width=c.sizes[0],s.height=c.sizes[1]):(s.width=c.sizes[0][0],s.height=c.sizes[0][1]),o.addBidResponse(i,s)}else s=d.createBid(2),s.bidderCode="sovrn",o.addBidResponse(i,s);else s=d.createBid(2),s.bidderCode="sovrn",o.addBidResponse(i,s)}),c(t)}else c([]);else c([])},{callBids:e}};e.exports=c},function(e,t,r){var i=r(7),n=r(5),a=r(8),d=function(){function e(e){"undefined"==typeof window.pp?a.loadScript(o,function(){t(e)}):t(e)}function t(e){for(var t=e.bids,i=0;i0&&void 0!==e.seatbid[0].bid[0]){for(var t=e.seatbid[0].bid[0],r=n.getPlacementIdByCBIdentifer(t.impid),a=i.createBid(1),d=0;du;u++)i=s[u],i.adxDomain&&o&&(o=!1,c.unshift("//"+i.adxDomain+"/adx/?rp=4")),c.push(t(i.params));o&&c.unshift("//adx.adform.net/adx/?rp=4"),pbjs[d]=r(s),c.push("callback=pbjs."+d),a.loadScript(c.join("&"))}function t(e){for(var t,r=[],n=["mid","inv","pdom","mname","mkw","mkv","cat","bcat","bcatrt","adv","advt","cntr","cntrt","maxp","minp","sminp","w","h","pb","pos","cturl","iturl","cttype","hidedomain","cdims","test"],a=0,d=n.length;d>a;a++)t=n[a],e.hasOwnProperty(t)&&r.push(t,"=",e[t],"&");return i(r.join(""))}function r(e){function t(e,t){for(var r=0,i=t.length;i>r;r++)if(e.width===t[r][0]&&e.height===t[r][1])return!0;return!1}return function(r){for(var i,n,a,s="adform",c=0,u=r.length;u>c;c++)n=r[c],a=e[c],n&&"banner"===n.response&&t(n,a.sizes)?(i=o.createBid(1),i.bidderCode=s,i.cpm=n.win_bid,i.cur=n.win_cur,i.ad=n.banner,i.width=n.width,i.height=n.height,d.addBidResponse(a.placementCode,i)):(i=o.createBid(2),i.bidderCode=s,d.addBidResponse(a.placementCode,i))}}function i(e){var t,r,i,n,a,d,o,c=[],u=0,p="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_=";for(e=s(e);u>2,a=(3&t)<<4|r>>4,d=(15&r)<<2|i>>6,o=63&i,isNaN(r)?d=o=64:isNaN(i)&&(o=64),c.push(p.charAt(n),p.charAt(a)),64!=d&&c.push(p.charAt(d)),64!=o&&c.push(p.charAt(o));return c.join("")}function s(e){e=e.replace(/\r\n/g,"\n");for(var t="",r=0;ri?t+=String.fromCharCode(i):i>127&&2048>i?(t+=String.fromCharCode(i>>6|192),t+=String.fromCharCode(63&i|128)):(t+=String.fromCharCode(i>>12|224),t+=String.fromCharCode(i>>6&63|128),t+=String.fromCharCode(63&i|128))}return t}return{callBids:e}}var n=r(3),a=r(8),d=r(5),o=r(7);e.exports=i},function(e,t,r){function i(){if(w&&"function"==typeof window[_]){for(var e=0;e=0&&200>e?t="0-200ms":e>=200&&300>e?t="200-300ms":e>=300&&400>e?t="300-400ms":e>=400&&500>e?t="400-500ms":e>=500&&600>e?t="500-600ms":e>=600&&800>e?t="600-800ms":e>=800&&1e3>e?t="800-1000ms":e>=1e3&&1200>e?t="1000-1200ms":e>=1200&&1500>e?t="1200-1500ms":e>=1500&&2e3>e?t="1500-2000ms":e>=2e3&&(t="2000ms above"),t}function d(e){var t;return e>=0&&.5>e?t="$0-0.5":e>=.5&&1>e?t="$0.5-1":e>=1&&1.5>e?t="$1-1.5":e>=1.5&&2>e?t="$1.5-2":e>=2&&2.5>e?t="$2-2.5":e>=2.5&&3>e?t="$2.5-3":e>=3&&4>e?t="$3-4":e>=4&&6>e?t="$4-6":e>=6&&8>e?t="$6-8":e>=8&&(t="$8 above"),t}function o(e){e&&e.bidderCode&&y.push(function(){I++,window[_]("send","event",B,"Requests",e.bidderCode,1,v)}),i()}function s(e){e&&e.bidderCode&&y.push(function(){var t=n(e.cpm),r=e.bidderCode;if("undefined"!=typeof e.timeToRespond&&C){I++;var i=a(e.timeToRespond);window[_]("send","event","Prebid.js Load Time Distribution",i,r,1,v)}if(e.cpm>0){I+=2;var o=d(e.cpm);C&&(I++,window[_]("send","event","Prebid.js CPM Distribution",o,r,1,v)),window[_]("send","event",B,"Bids",r,t,v),window[_]("send","event",B,"Bid Load Time",r,e.timeToRespond,v)}}),i()}function c(e){e&&e.bidder&&y.push(function(){l._each(E,function(t){e.bidder===t&&(I++,window[_]("send","event",B,"Timeouts",t,e.timeToRespond,v))})}),i()}function u(e){var t=n(e.cpm);y.push(function(){I++,window[_]("send","event",B,"Wins",e.bidderCode,t,v)}),i()}var p=r(6),l=r(3),f=r(4),g=f.EVENTS.BID_REQUESTED,m=f.EVENTS.BID_TIMEOUT,h=f.EVENTS.BID_RESPONSE,b=f.EVENTS.BID_WON,v={nonInteraction:!0},y=[],_=null,w=!0,B="Prebid.js Bids",I=0,C=!1,E=[];t.enableAnalytics=function(e){_="undefined"!=typeof e.global?e.global:"ga","undefined"!=typeof e.enableDistribution&&(C=e.enableDistribution);var t=null,r=p.getEvents();l._each(r,function(e){var r=e.args;if(e)if(e.eventType===g)t=r[0],o(t);else if(e.eventType===h)t=r[1],s(t);else if(e.eventType===m){var i=r[0];E=i}else e.eventType===b&&(t=r[0],u(t))}),p.on(g,function(e){o(e)}),p.on(h,function(e,t){s(t),c(t)}),p.on(m,function(e){E=e}),p.on(b,function(e){u(e)})}},function(e,t,r){function i(){for(var e=0;e0&&i.push({cpm:d.cpm,bid:d}),t.push(n)}}if(r&&0!==i.length){var o=u(i),s=o.adserverTargeting;j[r]=_.extend(j[r],s)}return t}function g(e){var t={};if(e){var r=JSON.stringify(e);t=JSON.parse(r),delete t.pbLg,delete t.pbMg,delete t.pbHg}return t}function m(){w.clearAllBidResponses(),O={},N=[],j={},z=!1}function h(e){var t=e;m(),n(t)}function b(e){var t=null;return e&&(t=v.getAdserverTargetingForAdUnitCode(e.getSlotElementId()),t||(t=v.getAdserverTargetingForAdUnitCode(e.getAdUnitPath()))),t}window.pbjs=window.pbjs||{},window.pbjs.que=window.pbjs.que||[];var v=window.pbjs,y=r(4),_=r(3),w=r(5),B=r(1),I=r(7),C=r(8),E=r(20),S=r(6),A="function",T="undefined",R="object",D="string",P=y.EVENTS.BID_WON,x=y.EVENTS.BID_TIMEOUT,U=[],N=[],O={},j={},M={},z=!1;v.bidderTimeout=v.bidderTimeout||3e3,v.logging=v.logging||!1,v.libLoaded=!0,v.adUnits=v.adUnits||[],v.que.push=function(e){if(typeof e===A)try{e.call()}catch(t){_.logError("Error processing command :"+t.message)}else _.logError("Commands written into pbjs.que.push must wrapped in a function")},v.getAdserverTargetingForAdUnitCodeStr=function(e){if(e){var t=v.getAdserverTargetingForAdUnitCode(e);return _.transformAdServerTargetingObj(t)}_.logMessage("Need to call getAdserverTargetingForAdUnitCodeStr with adunitCode")},v.getAdserverTargetingForAdUnitCode=function(e){return v.getBidResponses(e),e?j[e]:j},v.getAdserverTargeting=function(){return v.getAdserverTargetingForAdUnitCode()},v.getBidResponses=function(e){var t={},r=[],i={};if(e)t=l(e),r=[],t&&t.bids&&(r=f(t.bids)),i={bids:r};else{t=l();for(var n in t)t.hasOwnProperty(n)&&(t&&t[n]&&t[n].bids&&(r=f(t[n].bids)),i[n]={bids:r})}return i},v.getBidResponsesForAdUnitCode=function(e){return v.getBidResponses(e)},v.setTargetingForAdUnitsGPTAsync=function(e){if(!window.googletag||!_.isFn(window.googletag.pubads)||!_.isFn(window.googletag.pubads().getSlots))return _.logError("window.googletag is not defined on the page"),void 0;d();var t=e;typeof e===D?t=[e]:typeof e===R&&(t=e);var r={},i=0;if(t)for(i=0;i'),e.close(),e.defaultView&&e.defaultView.frameElement&&(e.defaultView.frameElement.width=n,e.defaultView.frameElement.height=i)):_.logError("Error trying to write ad. No ad for bid response id: "+t)}else _.logError("Error trying to write ad. Cannot find ad by given id : "+t)}catch(o){_.logError("Error trying to write ad Id :"+t+" to the page:"+o.message)}else _.logError("Error trying to write ad Id :"+t+" to the page. Missing document or adId")},v.requestBidsForAdUnit=function(e){m(),n(e)},v.requestBidsForAdUnits=function(e){if(!e||e.constructor!==Array)return _.logError("requestBidsForAdUnits must pass an array of adUnits"),void 0;m();var t=v.adUnits.slice(0);v.adUnits=e,n(),v.adUnits=t},v.removeAdUnit=function(e){if(e)for(var t=0;t",a+='',a+="",a=n.replaceTokenInString(a,d,"%%")}var s={};return window.pbjs=window.pbjs||{que:[]},window.pbjs.handleRubiconCallback=function(e){var r="",o={};if(e&&"ok"===e.status)try{var c="",u=d.getPlacementIdByCBIdentifer(t(e));if(u&&(r=u.placementCode,u.status=i.STATUS.GOOD,c=u.iframeId),e.ads&&e.ads[0]&&"ok"===e.ads[0].status){o=a.createBid(1);var p,l=e.ads[0],f=s[l.size_id],g=0,m=0,h=window.frames[c];if(p=h.contentWindow?h.contentWindow.RubiconAdServing:h.window.RubiconAdServing,p&&p.AdSizes){f=p.AdSizes[l.size_id];var b=f.dim.split("x");g=b[0],m=b[1]}o.cpm=l.cpm,o.ad="",o.ad_id=l.ad_id,o.bidderCode="rubicon",o.sizeId=l.size_id,o.width=g,o.height=m}else{o=a.createBid(2),o.bidderCode="rubicon";var u=d.getPlacementIdByCBIdentifer(t(e));u&&(r=u.placementCode)}}catch(v){n.logError("Error parsing rubicon response bid: "+v.message)}else{o=a.createBid(2),o.bidderCode="rubicon";var u=d.getPlacementIdByCBIdentifer(t(e));u&&(r=u.placementCode)}d.addBidResponse(r,o)},{callBids:e}};e.exports=o}]); \ No newline at end of file diff --git a/src/constants.json b/src/constants.json index 04f7e7d7200..522ee7ccaf4 100644 --- a/src/constants.json +++ b/src/constants.json @@ -17,7 +17,8 @@ "CB" : { "TYPE" : { "ALL_BIDS_BACK" : "allRequestedBidsBack", - "AD_UNIT_BIDS_BACK" : "adUnitBidsBack" + "AD_UNIT_BIDS_BACK" : "adUnitBidsBack", + "BID_WON": "bidWon" } }, "objectType_function" : "function", @@ -33,5 +34,8 @@ "BID_REQUESTED" : "bidRequested", "BID_RESPONSE" : "bidResponse", "BID_WON" : "bidWon" + }, + "EVENT_ID_PATHS": { + "bidWon": "adUnitCode" } } diff --git a/src/events.js b/src/events.js index 068104da894..9ff874daa25 100644 --- a/src/events.js +++ b/src/events.js @@ -2,90 +2,143 @@ * events.js */ var utils = require('./utils'), -CONSTANTS = require('./constants'), - slice = Array.prototype.slice; + CONSTANTS = require('./constants'), + slice = Array.prototype.slice, + push = Array.prototype.push; //define entire events //var allEvents = ['bidRequested','bidResponse','bidWon','bidTimeout']; -var allEvents = utils._map(CONSTANTS.EVENTS, function (v){ return v; }); +var allEvents = utils._map(CONSTANTS.EVENTS, function (v) { + return v; +}); +var idPaths = CONSTANTS.EVENT_ID_PATHS; + //keep a record of all events fired var eventsFired = []; -module.exports = (function (){ - - var _handlers = {}, - _public = {}; - - function _dispatch(event, args) { - utils.logMessage('Emitting event for: ' + event ); - //record the event: - eventsFired.push({ - eventType : event, - args : args - }); - utils._each(_handlers[event], function (fn) { - if (!fn) return; - try{ - fn.apply(null, args); - } - catch(e){ - utils.logError('Error executing handler:', 'events.js', e); - } - - }); - } - - function _checkAvailableEvent(event){ - return utils.contains(allEvents,event); - } - - _public.on = function (event, handler) { - - //check whether available event or not - if(_checkAvailableEvent(event)){ - _handlers[event] = _handlers[event] || []; - _handlers[event].push(handler); - } - else{ - utils.logError('Wrong event name : ' + event + ' Valid event names :' + allEvents); - } - }; - - _public.emit = function (event) { - var args = slice.call(arguments, 1); - _dispatch(event, args); - }; - - _public.off = function (event, id, handler) { - if (utils.isEmpty(_handlers[event])) { - return; - } - - utils._each(_handlers[event],function(h){ - if(h[id] !== null && h[id] !== undefined ){ - if(typeof handler === 'undefined' || h[id] === handler){ - h[id] = null; - } - } - }); - }; - - _public.get = function(){ - return _handlers; - }; - - /** - * This method can return a copy of all the events fired - * @return {array[object]} array of events fired - */ - _public.getEvents = function(){ - var arrayCopy = []; - utils._each(eventsFired, function(value){ - var newProp = utils.extend({}, value); - arrayCopy.push(newProp); - }); - return arrayCopy; - }; - - return _public; +module.exports = (function () { + + var _handlers = {}, + _public = {}; + + /** + * + * @param {String} eventString The name of the event. + * @param {Array} args The payload emitted with the event. + * @private + */ + function _dispatch(eventString, args) { + utils.logMessage('Emitting event for: ' + eventString); + + var eventPayload = args[0]; + var idPath = idPaths[eventString]; + var key = eventPayload[idPath]; + var event = _handlers[eventString] || {que: []}; + var eventKeys = utils._map(event, function (v, k) { + return k; + }); + var callbacks = []; + + //record the event: + eventsFired.push({ + eventType: eventString, + args: eventPayload, + id: key + }); + + /** Push each specific callback to the `callbacks` array. + * If the `event` map has a key that matches the value of the + * event payload id path, e.g. `eventPayload[idPath]`, then apply + * each function in the `que` array as an argument to push to the + * `callbacks` array + * */ + if (key && utils.contains(eventKeys, key)) { + push.apply(callbacks, event[key].que); + } + + /** Push each general callback to the `callbacks` array. */ + push.apply(callbacks, event.que); + + /** call each of the callbacks */ + utils._each(callbacks, function (fn) { + if (!fn) return; + try { + fn.apply(null, args); + } + catch (e) { + utils.logError('Error executing handler:', 'events.js', e); + } + }); + } + + function _checkAvailableEvent(event) { + return utils.contains(allEvents, event); + } + + _public.on = function (eventString, handler, id) { + + //check whether available event or not + if (_checkAvailableEvent(eventString)) { + var event = _handlers[eventString] || { que: [] }; + + if (id) { + event[id] = event[id] || { que: [] }; + event[id].que.push(handler); + } else { + event.que.push(handler); + } + _handlers[eventString] = event; + } + else { + utils.logError('Wrong event name : ' + eventString + ' Valid event names :' + allEvents); + } + }; + + _public.emit = function (event) { + var args = slice.call(arguments, 1); + _dispatch(event, args); + }; + + _public.off = function (eventString, handler, id) { + var event = _handlers[eventString]; + + if (utils.isEmpty(event) || utils.isEmpty(event.que) && utils.isEmpty(event[id])) { return; } + if (id && (utils.isEmpty(event[id]) || utils.isEmpty(event[id].que))) { return; } + + if (id) { + utils._each(event[id].que, function (_handler) { + var que = event[id].que; + if (_handler === handler) { + que.splice(utils.indexOf.call(que, _handler), 1); + } + }); + } else { + utils._each(event.que, function (_handler) { + var que = event.que; + if (_handler === handler) { + que.splice(utils.indexOf.call(que, _handler), 1); + } + }); + } + _handlers[eventString] = event; + }; + + _public.get = function () { + return _handlers; + }; + + /** + * This method can return a copy of all the events fired + * @return {Array} array of events fired + */ + _public.getEvents = function () { + var arrayCopy = []; + utils._each(eventsFired, function (value) { + var newProp = utils.extend({}, value); + arrayCopy.push(newProp); + }); + return arrayCopy; + }; + + return _public; }()); diff --git a/src/polyfills.js b/src/polyfills.js new file mode 100644 index 00000000000..6c3e29cf43f --- /dev/null +++ b/src/polyfills.js @@ -0,0 +1,70 @@ +/** + * returns index of search element in array + * used by `utils` when Array.prototype.indexOf is unavailable (e.g. < IE9) + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf#Polyfill + * + * @param searchElement + * @param fromIndex + * @returns {*} + */ +exports.indexOf = function(searchElement, fromIndex) { + + var k; + + // 1. Let o be the result of calling ToObject passing + // the this value as the argument. + if (this == null) { + throw new TypeError('"this" is null or not defined'); + } + + var o = Object(this); + + // 2. Let lenValue be the result of calling the Get + // internal method of o with the argument "length". + // 3. Let len be ToUint32(lenValue). + var len = o.length >>> 0; + + // 4. If len is 0, return -1. + if (len === 0) { + return -1; + } + + // 5. If argument fromIndex was passed let n be + // ToInteger(fromIndex); else let n be 0. + var n = +fromIndex || 0; + + if (Math.abs(n) === Infinity) { + n = 0; + } + + // 6. If n >= len, return -1. + if (n >= len) { + return -1; + } + + // 7. If n >= 0, then Let k be n. + // 8. Else, n<0, Let k be len - abs(n). + // If k is less than 0, then let k be 0. + k = Math.max(n >= 0 ? n : len - Math.abs(n), 0); + + // 9. Repeat, while k < len + while (k < len) { + // a. Let Pk be ToString(k). + // This is implicit for LHS operands of the in operator + // b. Let kPresent be the result of calling the + // HasProperty internal method of o with argument Pk. + // This step can be combined with c + // c. If kPresent is true, then + // i. Let elementK be the result of calling the Get + // internal method of o with the argument ToString(k). + // ii. Let same be the result of applying the + // Strict Equality Comparison Algorithm to + // searchElement and elementK. + // iii. If same is true, return k. + if (k in o && o[k] === searchElement) { + return k; + } + k++; + } + return -1; +}; diff --git a/src/prebid.js b/src/prebid.js index d2957293017..4f40fa27468 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -18,7 +18,6 @@ var objectType_function = 'function'; var objectType_undefined = 'undefined'; var objectType_object = 'object'; var objectType_string = 'string'; -var objectType_number = 'number'; var BID_WON = CONSTANTS.EVENTS.BID_WON; var BID_TIMEOUT = CONSTANTS.EVENTS.BID_TIMEOUT; @@ -29,6 +28,9 @@ var pb_preBidders = [], pb_keyHistoryMap = {}, pb_bidsTimedOut = false; +var eventValidators = { + 'bidWon': checkDefinedPlacement +}; /* Public vars */ //default timeout for all bids @@ -68,7 +70,7 @@ function processQue() { catch(e){ utils.logError('Error processing command :', 'prebid.js', e); } - + } } } @@ -122,7 +124,7 @@ function timeOutBidders(){ if(!pb_bidsTimedOut){ pb_bidsTimedOut = true; var timedOutBidders = bidmanager.getTimedOutBidders(); - events.emit(BID_TIMEOUT, timedOutBidders); + events.emit(BID_TIMEOUT, timedOutBidders); } } @@ -248,7 +250,7 @@ function getBidResponsesByAdUnit(adunitCode) { if (adunitCode) { returnObj = bidmanager.pbBidResponseByPlacement[adunitCode]; return returnObj; - } + } else { return bidmanager.pbBidResponseByPlacement; } @@ -327,6 +329,17 @@ function requestAllBids(tmout){ init(timeout); } +function checkDefinedPlacement(id) { + var placementCodes = utils._map(pb_placements, function (placement) { + return placement.code; + }); + + if (!utils.contains(placementCodes, id)) { + utils.logError('The "' + id + '" placement is not defined.'); + return; + } + return true; +} ////////////////////////////////// // // @@ -435,7 +448,7 @@ pbjs.setTargetingForAdUnitsGPTAsync = function(codeArr) { return; } - //emit bid timeout event here + //emit bid timeout event here timeOutBidders(); var adUnitCodesArr = codeArr; @@ -658,7 +671,7 @@ pbjs.requestBids = function(requestObj) { /** * * Add adunit(s) - * @param {(string|string[])} Array of adUnits or single adUnit Object. + * @param {Array|String} adUnitArr Array of adUnits or single adUnit Object. * @alias module:pbjs.addAdUnits */ pbjs.addAdUnits = function(adUnitArr) { @@ -670,10 +683,50 @@ pbjs.addAdUnits = function(adUnitArr) { } }; +/** + * @param {String} event the name of the event + * @param {Function} handler a callback to set on event + * @param {String} id an identifier in the context of the event + * + * This API call allows you to register a callback to handle a Prebid.js event. + * An optional `id` parameter provides more finely-grained event callback registration. + * This makes it possible to register callback events for a specific item in the + * event context. For example, `bidWon` events will accept an `id` for ad unit code. + * `bidWon` callbacks registered with an ad unit code id will be called when a bid + * for that ad unit code wins the auction. Without an `id` this method registers the + * callback for every `bidWon` event. + * + * Currently `bidWon` is the only event that accepts an `id` parameter. + */ +pbjs.onEvent = function(event, handler, id) { + if(!utils.isFn(handler)) { + utils.logError('The event handler provided is not a function and was not set on event "' + event + '".'); + return; + } + if(id && !eventValidators[event].call(null, id)){ + utils.logError('The id provided is not valid for event "' + event + '" and no handler was set.'); + return; + } + + events.on(event, handler, id); +}; + +/** + * @param {String} event the name of the event + * @param {Function} handler a callback to remove from the event + * @param {String} id an identifier in the context of the event (see `pbjs.onEvent`) + */ +pbjs.offEvent = function(event, handler, id){ + if(id && !eventValidators[event].call(null, id)){ + return; + } + + events.off(event, handler, id); +}; /** * Add a callback event - * @param {String} event event to attach callback to Options: "allRequestedBidsBack" | "adUnitBidsBack" + * @param {String} eventStr event to attach callback to Options: "allRequestedBidsBack" | "adUnitBidsBack" * @param {Function} func function to execute. Paramaters passed into the function: (bidResObj), [adUnitCode]); * @alias module:pbjs.addCallback * @returns {String} id for callback @@ -741,10 +794,10 @@ pbjs.registerBidAdapter = function(bidderAdaptor, bidderCode){ responseObj.bids.push(bid); responseObj.bidsReceivedCount++; bidmanager.pbBidResponseByPlacement[adunitCode] = responseObj; - }; + } bidmanager.increaseBidResponseReceivedCount(bidderCode); -} +}; /** * Wrapper to bidfactory.createBid() @@ -779,7 +832,7 @@ pbjs.loadScript = function(tagSrc, callback){ * return data for analytics * @param {Function} [description] * @return {[type]} [description] - + pbjs.getAnalyticsData = function(){ var returnObj = {}; var bidResponses = pbjs.getBidResponses(); @@ -810,7 +863,7 @@ pbjs.getAnalyticsData = function(){ if(bid.bidderCode!==''){ var returnBids = returnObj[bid.bidderCode].bids; var returnIdx = 0; - + for(var j=0;j Date: Fri, 19 Feb 2016 15:55:36 -0800 Subject: [PATCH 039/160] Build only with required adapters. --- gulpfile.js | 3 +-- karma.conf.js | 6 +++--- webpack.conf.js | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index 5f51cb895c9..d89726ebdfd 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -49,11 +49,10 @@ gulp.task('webpack', function() { webpackConfig.output.filename = 'prebid.' + argv.tag + '.js'; } - return gulp.src('src/**/*.js') + return gulp.src('src/*.js') .pipe(webpack(webpackConfig)) .pipe(header(banner, {pkg: pkg})) .pipe(gulp.dest('build/dev')) - .pipe(gulp.dest('test/app')) .pipe(uglify()) .pipe(gulp.dest('build/dist')) .pipe(connect.reload()); diff --git a/karma.conf.js b/karma.conf.js index d04d1e30f01..595329a0b90 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -3,7 +3,7 @@ var webpackConfig = require('./webpack.conf'); webpackConfig.module.postLoaders = [{ test: /\.js$/, - exclude: /(mediation-script)|(node_modules)|(test)|(polyfill)|(visibly)/, + exclude: /(node_modules)|(test)|(integrationExamples)|(build)/, loader: 'istanbul-instrumenter' }]; @@ -31,7 +31,7 @@ module.exports = function(config) { // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor preprocessors: { 'test/**/*_spec.js': ['webpack'], - 'src/**/*.js': ['coverage'] + 'src/*.js': ['coverage'] }, // WebPack Related @@ -107,4 +107,4 @@ module.exports = function(config) { 'karma-ie-launcher' ] }); -}; \ No newline at end of file +}; diff --git a/webpack.conf.js b/webpack.conf.js index 49028e252af..a785e8e9115 100644 --- a/webpack.conf.js +++ b/webpack.conf.js @@ -4,7 +4,7 @@ module.exports = { filename: 'prebid.js' }, resolve: { - modulesDirectories: ['', 'node_modules', 'src', 'adapters'] + modulesDirectories: ['', 'node_modules', 'src'] }, resolveLoader: { modulesDirectories: ['loaders', 'node_modules'] From 858aadcec9ea962ecfbfdd7bb0bb9dc4262e73a2 Mon Sep 17 00:00:00 2001 From: protonate Date: Fri, 19 Feb 2016 23:11:42 -0800 Subject: [PATCH 040/160] Code Quality and Style Standards Define jscs and jshint settings to report on code quality and style standards. Updates code files to standards. In some cases adapters were also updated, while some jshint options are set to be ignored. This is not set to enforce quality at commit or build time ... yet. We will want to set that in place soon. Also, add support for babel and es6 syntax. See the file `test/helpers/pbjs-test-only.js` for an example of an ES6 module. This file is imported using ES6 syntax in the `test/spec/aliasBidder_spec.js` file and required the ES5 way in `test/spec/adUnits_spec.js`. --- .babelrc | 1 + .gitignore | 2 +- .jscsrc | 6 + .jshintrc | 13 +- gulpHelpers.js | 28 +- gulpfile.js | 160 ++--- karma.conf.js | 200 +++--- package.json | 5 + src/adaptermanager.js | 144 ++-- src/adapters/adapter.js | 34 +- src/adapters/adform.js | 252 +++---- src/adapters/aol.js | 359 +++++----- src/adapters/appnexus.js | 423 ++++++------ src/adapters/indexExchange.js | 745 ++++++++++---------- src/adapters/openx.js | 197 +++--- src/adapters/pubmatic.js | 218 +++--- src/adapters/pulsepoint.js | 100 +-- src/adapters/rubicon.js | 503 +++++++------- src/adapters/rubiconLegacy.js | 362 +++++----- src/adapters/sovrn.js | 363 +++++----- src/adapters/springserve.js | 180 ++--- src/adapters/yieldbot.js | 248 +++---- src/adloader.js | 99 +-- src/bidfactory.js | 89 ++- src/bidmanager.js | 673 +++++++++--------- src/constants.json | 76 +- src/events.js | 265 +++---- src/ga.js | 396 +++++------ src/polyfills.js | 102 +-- src/prebid.js | 1141 +++++++++++++++---------------- src/utils.js | 545 ++++++++------- styleRules.jscs.json | 5 - test/helpers/pbjs-test-only.js | 10 + test/spec/adUnits_spec.js | 226 +++--- test/spec/adloader_spec.js | 16 +- test/spec/aliasBidder_spec.js | 69 +- test/spec/api_spec.js | 75 +- test/spec/unit/pbjs_api_spec.js | 247 +++---- test/spec/utils_spec.js | 933 ++++++++++++------------- webpack.conf.js | 46 +- 40 files changed, 4849 insertions(+), 4707 deletions(-) create mode 100644 .babelrc create mode 100644 .jscsrc delete mode 100644 styleRules.jscs.json create mode 100644 test/helpers/pbjs-test-only.js diff --git a/.babelrc b/.babelrc new file mode 100644 index 00000000000..9d8d5165620 --- /dev/null +++ b/.babelrc @@ -0,0 +1 @@ +{ "presets": ["es2015"] } diff --git a/.gitignore b/.gitignore index dd8bce59397..1e4d5248000 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ # Built Files node_modules/ -build/coverage +build # Test Files test/app diff --git a/.jscsrc b/.jscsrc new file mode 100644 index 00000000000..a06349b1b41 --- /dev/null +++ b/.jscsrc @@ -0,0 +1,6 @@ +{ + "maxErrors": 5000, + "esnext": true, + "requireTrailingComma": false, + "requireCamelCaseOrUpperCaseIdentifiers": false +} diff --git a/.jshintrc b/.jshintrc index 8b366353381..11b31057327 100644 --- a/.jshintrc +++ b/.jshintrc @@ -1,10 +1,9 @@ { - "bitwise": true, + "bitwise": false, "browser": true, "curly": false, "devel": true, "eqeqeq": true, - "es3": true, "freeze": true, "immed": true, "maxdepth": 5, @@ -12,13 +11,19 @@ "noarg": true, "node": true, "notypeof": true, + "esnext": true, "trailing": true, "undef": true, "unused": true, + "strict": false, "scripturl": true, "globals": { + "before": true, + "after": true, + "exports": true, + "pbjs": true, + "pbjsTestOnly": true, "assert": false, - "mediation": false, "expect": false, "dump": false, "describe": false, @@ -28,6 +33,6 @@ "sinon": false, "beforeEach": false, "afterEach": false, - "APN": false + "JSON": true } } diff --git a/gulpHelpers.js b/gulpHelpers.js index b80bab06a0b..fbf1896dc7d 100644 --- a/gulpHelpers.js +++ b/gulpHelpers.js @@ -1,14 +1,16 @@ module.exports = { - parseBrowserArgs: function(argv) { - return (argv.browsers) ? argv.browsers.split(',') : []; - }, - toCapitalCase: function(str) { - return str.charAt(0).toUpperCase() + str.slice(1); - }, - jsonifyHTML: function(str) { - console.log(arguments); - return str.replace(/\n/g, '') - .replace(/<\//g, '<\\/') - .replace(/\/>/g, '\\/>'); - } -}; \ No newline at end of file + parseBrowserArgs: function (argv) { + return (argv.browsers) ? argv.browsers.split(',') : []; + }, + + toCapitalCase: function (str) { + return str.charAt(0).toUpperCase() + str.slice(1); + }, + + jsonifyHTML: function (str) { + console.log(arguments); + return str.replace(/\n/g, '') + .replace(/<\//g, '<\\/') + .replace(/\/>/g, '\\/>'); + } +}; diff --git a/gulpfile.js b/gulpfile.js index d89726ebdfd..c584d4eeaab 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,4 +1,3 @@ -var fs = require('fs'); var argv = require('yargs').argv; var gulp = require('gulp'); var gutil = require('gulp-util'); @@ -8,18 +7,16 @@ var uglify = require('gulp-uglify'); var jshint = require('gulp-jshint'); var clean = require('gulp-clean'); var karma = require('gulp-karma'); -var replace = require('gulp-replace'); -var rename = require('gulp-rename'); var opens = require('open'); var webpackConfig = require('./webpack.conf.js'); var helpers = require('./gulpHelpers'); -var path = require('path'); var del = require('del'); -var gulpJsdoc2md = require("gulp-jsdoc-to-markdown"); -var concat = require("gulp-concat"); +var gulpJsdoc2md = require('gulp-jsdoc-to-markdown'); +var concat = require('gulp-concat'); var jscs = require('gulp-jscs'); var header = require('gulp-header'); var zip = require('gulp-zip'); +var babel = require('gulp-babel'); var CI_MODE = process.env.NODE_ENV === 'ci'; var prebidSrcLocation = './src/prebid.js'; @@ -35,103 +32,110 @@ gulp.task('serve', ['clean', 'quality', 'webpack', 'watch', 'test']); gulp.task('build', ['clean', 'quality', 'webpack', 'zip']); -gulp.task('clean', function() { - return gulp.src(['build', 'test/app/**/*.js', 'test/app/index.html'], { - read: false - }) - .pipe(clean()); +gulp.task('clean', function () { + return gulp.src(['build', 'test/app/**/*.js', 'test/app/index.html'], { + read: false + }) + .pipe(clean()); }); -gulp.task('webpack', function() { - - // change output filename if argument --tag given - if (argv.tag && argv.tag.length) { - webpackConfig.output.filename = 'prebid.' + argv.tag + '.js'; - } - - return gulp.src('src/*.js') - .pipe(webpack(webpackConfig)) - .pipe(header(banner, {pkg: pkg})) - .pipe(gulp.dest('build/dev')) - .pipe(uglify()) - .pipe(gulp.dest('build/dist')) - .pipe(connect.reload()); +gulp.task('webpack', function () { + + // change output filename if argument --tag given + if (argv.tag && argv.tag.length) { + webpackConfig.output.filename = 'prebid.' + argv.tag + '.js'; + } + + return gulp.src('src/**/*.js') + .pipe(header(banner, { pkg: pkg })) + .pipe(webpack(webpackConfig)) + .pipe(babel({ + presets: ['es2015'] + })) + .pipe(gulp.dest('build/dev')) + .pipe(uglify()) + .pipe(gulp.dest('build/dist')) + .pipe(connect.reload()); }); //zip up for release gulp.task('zip', ['jscs', 'clean', 'webpack'], function () { - return gulp.src(['build/dist/*', 'integrationExamples/gpt/*']) - .pipe(zip(packageNameVersion + '.zip')) - .pipe(gulp.dest('./')); + return gulp.src(['build/dist/*', 'integrationExamples/gpt/*']) + .pipe(zip(packageNameVersion + '.zip')) + .pipe(gulp.dest('./')); }); // Karma Continuous Testing // Pass your browsers by using --browsers=chrome,firefox,ie9 // Run CI by passing --watch -gulp.task('test', function() { - var defaultBrowsers = CI_MODE ? ['PhantomJS'] : ['Chrome']; - var browserArgs = helpers.parseBrowserArgs(argv).map(helpers.toCapitalCase); - - return gulp.src('lookAtKarmaConfJS') - .pipe(karma({ - browsers: (browserArgs.length > 0) ? browserArgs : defaultBrowsers, - configFile: 'karma.conf.js', - action: (argv.watch) ? 'watch' : 'run' - })); +gulp.task('test', function () { + var defaultBrowsers = CI_MODE ? ['PhantomJS'] : ['Chrome']; + var browserArgs = helpers.parseBrowserArgs(argv).map(helpers.toCapitalCase); + + return gulp.src('lookAtKarmaConfJS') + .pipe(babel({ + presets: ['es2015'] + })) + .pipe(karma({ + browsers: (browserArgs.length > 0) ? browserArgs : defaultBrowsers, + configFile: 'karma.conf.js', + action: (argv.watch) ? 'watch' : 'run' + })); }); // Small task to load coverage reports in the browser -gulp.task('coverage', function(done) { - var coveragePort = 1999; - - connect.server({ - port: 1999, - root: 'build/coverage', - livereload: false - }); - opens('http://localhost:' + coveragePort + '/coverage/'); - done(); +gulp.task('coverage', function (done) { + var coveragePort = 1999; + + connect.server({ + port: 1999, + root: 'build/coverage', + livereload: false + }); + opens('http://localhost:' + coveragePort + '/coverage/'); + done(); }); // Watch Task with Live Reload -gulp.task('watch', function() { - - gulp.watch(['test/spec/**/*.js'], ['webpack', 'test']); - gulp.watch(['integrationExamples/gpt/*.html'], ['test']); - gulp.watch(['src/**/*.js'], ['webpack', 'test']); - connect.server({ - port: 9999, - root: './', - livereload: true - }); +gulp.task('watch', function () { + + gulp.watch(['test/spec/**/*.js'], ['webpack', 'test']); + gulp.watch(['integrationExamples/gpt/*.html'], ['test']); + gulp.watch(['src/**/*.js'], ['webpack', 'test']); + connect.server({ + port: 9999, + root: './', + livereload: true + }); }); -gulp.task('quality', ['hint', 'jscs'], function() {}); +gulp.task('quality', ['hint', 'jscs'], function () { +}); -gulp.task('hint', function() { - return gulp.src('src/**/*.js') - .pipe(jshint('.jshintrc')) - .pipe(jshint.reporter('default')); +gulp.task('hint', function () { + return gulp.src('src/**/*.js') + .pipe(jshint('.jshintrc')) + .pipe(jshint.reporter('default')); }); -gulp.task('jscs', function() { - return gulp.src(prebidSrcLocation) - .pipe(jscs({ - 'configPath': 'styleRules.jscs.json' - })); +gulp.task('jscs', function () { + return gulp.src('src/**/*.js') + .pipe(jscs({ + configPath: '.jscsrc' + })); }); -gulp.task('clean-docs', function(){ - del(['docs']); +gulp.task('clean-docs', function () { + del(['docs']); }); -gulp.task("docs", ['clean-docs'], function() { - return gulp.src("src/prebid.js") - .pipe(concat("readme.md")) - .pipe(gulpJsdoc2md()) - .on("error", function(err){ - gutil.log("jsdoc2md failed:", err.message); - }) - .pipe(gulp.dest("docs")); +gulp.task('docs', ['clean-docs'], function () { + return gulp.src('src/prebid.js') + .pipe(concat('readme.md')) + .pipe(gulpJsdoc2md()) + .on('error', function (err) { + gutil.log('jsdoc2md failed:', err.message); + }) + .pipe(gulp.dest('docs')); }); diff --git a/karma.conf.js b/karma.conf.js index 595329a0b90..fc3c6b95fe1 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -1,110 +1,110 @@ // Karma configuration // Generated on Thu Aug 07 2014 09:45:28 GMT-0700 (PDT) var webpackConfig = require('./webpack.conf'); -webpackConfig.module.postLoaders = [{ +webpackConfig.module.postLoaders = [ + { test: /\.js$/, exclude: /(node_modules)|(test)|(integrationExamples)|(build)/, loader: 'istanbul-instrumenter' -}]; + } +]; var CI_MODE = process.env.NODE_ENV === 'ci'; -module.exports = function(config) { - config.set({ - - // base path that will be used to resolve all patterns (eg. files, exclude) - basePath: './', - - // frameworks to use - // available frameworks: https://npmjs.org/browse/keyword/karma-adapter - frameworks: ['es5-shim', 'mocha', 'expect', 'sinon'], - - // list of files / patterns to load in the browser - files: [ - 'test/**/*_spec.js' - ], - - // list of files to exclude - exclude: [], - - // preprocess matching files before serving them to the browser - // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor - preprocessors: { - 'test/**/*_spec.js': ['webpack'], - 'src/*.js': ['coverage'] - }, - - // WebPack Related - webpack: webpackConfig, - - // test results reporter to use - // possible values: 'dots', 'progress' - // available reporters: https://npmjs.org/browse/keyword/karma-reporter - reporters: CI_MODE ? ['junit', 'coverage'] : ['progress', 'html', 'nyan', 'coverage'], - - // junit reporter config - junitReporter: { - outputDir: 'test' - }, - - // optionally, configure the reporter - coverageReporter: { - reporters: [ - { type: 'html', dir:'./build/coverage/' }, - { type: 'text', dir:'./build/coverage/' }, - { type: 'lcov', dir:'./build/coverage/lcov', subdir: '.' } - ] - }, - - htmlReporter: { - outputDir: 'build/coverage/karma_html', // where to put the reports - urlFriendlyName: true, // simply replaces spaces with _ for files/dirs - reportName: 'report' // report summary filename; browser info by default - }, - - // web server port - port: 9876, - - // enable / disable colors in the output (reporters and logs) - colors: true, - - // level of logging - // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG - logLevel: config.LOG_INFO, - - - // enable / disable watching file and executing tests whenever any file changes - autoWatch: true, - - - // start these browsers - // NOTE: these get defined again in gulpfile.js for the gulp tasks - // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher - browsers: ['Chrome', 'Firefox'], - - // Continuous Integration mode - // if true, Karma captures browsers, runs the tests and exits - singleRun: false, - - plugins: [ - 'karma-phantomjs-launcher', - 'karma-nyan-reporter', - 'karma-coverage', - 'karma-es5-shim', - 'karma-mocha', - 'karma-expect', - 'karma-sinon', - 'karma-webpack', - 'karma-junit-reporter', - 'karma-html-reporter', - 'karma-chrome-launcher', - 'karma-sauce-launcher', - 'karma-firefox-launcher', - 'karma-opera-launcher', - 'karma-safari-launcher', - 'karma-script-launcher', - 'karma-requirejs', - 'karma-ie-launcher' - ] - }); +module.exports = function (config) { + config.set({ + + // base path that will be used to resolve all patterns (eg. files, exclude) + basePath: './', + + // frameworks to use + // available frameworks: https://npmjs.org/browse/keyword/karma-adapter + frameworks: ['es5-shim', 'mocha', 'expect', 'sinon'], + + // list of files / patterns to load in the browser + files: [ + 'test/**/*_spec.js' + ], + + // list of files to exclude + exclude: [], + + // preprocess matching files before serving them to the browser + // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor + preprocessors: { + 'test/**/*_spec.js': ['webpack'], + 'src/**/*.js': ['webpack', 'coverage'] + }, + + // WebPack Related + webpack: webpackConfig, + + // test results reporter to use + // possible values: 'dots', 'progress' + // available reporters: https://npmjs.org/browse/keyword/karma-reporter + reporters: CI_MODE ? ['junit', 'coverage'] : ['progress', 'html', 'nyan', 'coverage'], + + // junit reporter config + junitReporter: { + outputDir: 'test' + }, + + // optionally, configure the reporter + coverageReporter: { + reporters: [ + { type: 'html', dir: './build/coverage/' }, + { type: 'text', dir: './build/coverage/' }, + { type: 'lcov', dir: './build/coverage/lcov', subdir: '.' } + ] + }, + + htmlReporter: { + outputDir: 'build/coverage/karma_html', // where to put the reports + urlFriendlyName: true, // simply replaces spaces with _ for files/dirs + reportName: 'report' // report summary filename; browser info by default + }, + + // web server port + port: 9876, + + // enable / disable colors in the output (reporters and logs) + colors: true, + + // level of logging + // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG + logLevel: config.LOG_INFO, + + // enable / disable watching file and executing tests whenever any file changes + autoWatch: true, + + // start these browsers + // NOTE: these get defined again in gulpfile.js for the gulp tasks + // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher + browsers: ['Chrome', 'Firefox'], + + // Continuous Integration mode + // if true, Karma captures browsers, runs the tests and exits + singleRun: false, + + plugins: [ + 'karma-phantomjs-launcher', + 'karma-nyan-reporter', + 'karma-coverage', + 'karma-es5-shim', + 'karma-mocha', + 'karma-expect', + 'karma-sinon', + 'karma-webpack', + 'karma-junit-reporter', + 'karma-html-reporter', + 'karma-chrome-launcher', + 'karma-sauce-launcher', + 'karma-firefox-launcher', + 'karma-opera-launcher', + 'karma-safari-launcher', + 'karma-script-launcher', + 'karma-requirejs', + 'karma-ie-launcher' + ] + }); }; diff --git a/package.json b/package.json index cd24be13d0c..8b35d5fd54f 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,9 @@ "author": [], "license": "Apache-2.0", "devDependencies": { + "babel-core": "^6.5.2", + "babel-loader": "^6.2.3", + "babel-preset-es2015": "^6.5.0", "chai": "^3.3.0", "chai-as-promised": "^5.1.0", "clone": "^0.1.17", @@ -18,6 +21,7 @@ "express": "^4.10.2", "fuzzyset.js": "0.0.1", "gulp": "^3.8.7", + "gulp-babel": "^6.1.2", "gulp-clean": "^0.3.1", "gulp-concat": "^2.6.0", "gulp-connect": "^2.0.6", @@ -38,6 +42,7 @@ "jquery": "1.11.1", "json-loader": "^0.5.1", "karma": "^0.13.2", + "karma-babel-preprocessor": "^6.0.1", "karma-chai": "^0.1.0", "karma-chrome-launcher": "^0.2.0", "karma-coverage": "^0.2.6", diff --git a/src/adaptermanager.js b/src/adaptermanager.js index 4d7522c719b..b7d29216134 100644 --- a/src/adaptermanager.js +++ b/src/adaptermanager.js @@ -19,87 +19,85 @@ var events = require('./events'); var _bidderRegistry = {}; exports.bidderRegistry = _bidderRegistry; - -exports.callBids = function(bidderArr) { - for (var i = 0; i < bidderArr.length; i++) { - //use the bidder code to identify which function to call - var bidder = bidderArr[i]; - if (bidder.bidderCode && _bidderRegistry[bidder.bidderCode]) { - utils.logMessage('CALLING BIDDER ======= ' + bidder.bidderCode); - var currentBidder = _bidderRegistry[bidder.bidderCode]; - //emit 'bidRequested' event - events.emit(CONSTANTS.EVENTS.BID_REQUESTED, bidder); - currentBidder.callBids(bidder); - - // if the bidder didn't explicitly set the number of bids - // expected, default to the number of bids passed into the bidder - if (bidmanager.getExpectedBidsCount(bidder.bidderCode) === undefined) { - bidmanager.setExpectedBidsCount(bidder.bidderCode, bidder.bids.length); - } - - var currentTime = new Date().getTime(); - bidmanager.registerBidRequestTime(bidder.bidderCode, currentTime); - - if (currentBidder.defaultBidderSettings) { - bidmanager.registerDefaultBidderSetting(bidder.bidderCode, currentBidder.defaultBidderSettings); - } - } - else{ - utils.logError('Adapter trying to be called which does not exist: ' + bidder.bidderCode, 'adaptermanager.callBids'); - } - } +exports.callBids = function (bidderArr) { + for (var i = 0; i < bidderArr.length; i++) { + //use the bidder code to identify which function to call + var bidder = bidderArr[i]; + if (bidder.bidderCode && _bidderRegistry[bidder.bidderCode]) { + utils.logMessage('CALLING BIDDER ======= ' + bidder.bidderCode); + var currentBidder = _bidderRegistry[bidder.bidderCode]; + + //emit 'bidRequested' event + events.emit(CONSTANTS.EVENTS.BID_REQUESTED, bidder); + currentBidder.callBids(bidder); + + // if the bidder didn't explicitly set the number of bids + // expected, default to the number of bids passed into the bidder + if (bidmanager.getExpectedBidsCount(bidder.bidderCode) === undefined) { + bidmanager.setExpectedBidsCount(bidder.bidderCode, bidder.bids.length); + } + + var currentTime = new Date().getTime(); + bidmanager.registerBidRequestTime(bidder.bidderCode, currentTime); + + if (currentBidder.defaultBidderSettings) { + bidmanager.registerDefaultBidderSetting(bidder.bidderCode, currentBidder.defaultBidderSettings); + } + } else { + utils.logError('Adapter trying to be called which does not exist: ' + bidder.bidderCode, 'adaptermanager.callBids'); + } + } }; +exports.registerBidAdapter = function (bidAdaptor, bidderCode) { + if (bidAdaptor && bidderCode) { -exports.registerBidAdapter = function(bidAdaptor, bidderCode) { - if (bidAdaptor && bidderCode) { - - if (typeof bidAdaptor.callBids === CONSTANTS.objectType_function) { - _bidderRegistry[bidderCode] = bidAdaptor; + if (typeof bidAdaptor.callBids === CONSTANTS.objectType_function) { + _bidderRegistry[bidderCode] = bidAdaptor; - } else { - utils.logError('Bidder adaptor error for bidder code: ' + bidderCode + 'bidder must implement a callBids() function'); - } + } else { + utils.logError('Bidder adaptor error for bidder code: ' + bidderCode + 'bidder must implement a callBids() function'); + } - } else { - utils.logError('bidAdaptor or bidderCode not specified'); - } + } else { + utils.logError('bidAdaptor or bidderCode not specified'); + } }; -exports.aliasBidAdapter = function(bidderCode, alias){ - var existingAlias = _bidderRegistry[alias]; - - if(typeof existingAlias === CONSTANTS.objectType_undefined){ - var bidAdaptor = _bidderRegistry[bidderCode]; - - if(typeof bidAdaptor === CONSTANTS.objectType_undefined){ - utils.logError('bidderCode "' + bidderCode + '" is not an existing bidder.', 'adaptermanager.aliasBidAdapter'); - }else{ - try{ - var newAdapter = bidAdaptor.createNew(); - newAdapter.setBidderCode(alias); - this.registerBidAdapter(newAdapter,alias); - }catch(e){ - utils.logError(bidderCode + ' bidder does not currently support aliasing.', 'adaptermanager.aliasBidAdapter'); - } - } - }else{ - utils.logMessage('alias name "' + alias + '" has been already specified.'); - } +exports.aliasBidAdapter = function (bidderCode, alias) { + var existingAlias = _bidderRegistry[alias]; + + if (typeof existingAlias === CONSTANTS.objectType_undefined) { + var bidAdaptor = _bidderRegistry[bidderCode]; + + if (typeof bidAdaptor === CONSTANTS.objectType_undefined) { + utils.logError('bidderCode "' + bidderCode + '" is not an existing bidder.', 'adaptermanager.aliasBidAdapter'); + } else { + try { + var newAdapter = bidAdaptor.createNew(); + newAdapter.setBidderCode(alias); + this.registerBidAdapter(newAdapter, alias); + } catch (e) { + utils.logError(bidderCode + ' bidder does not currently support aliasing.', 'adaptermanager.aliasBidAdapter'); + } + } + } else { + utils.logMessage('alias name "' + alias + '" has been already specified.'); + } }; - // Register the bid adaptors here -this.registerBidAdapter(RubiconAdapter(), 'rubicon'); -this.registerBidAdapter(AppNexusAdapter.createNew(), 'appnexus'); -this.registerBidAdapter(OpenxAdapter(), 'openx'); -this.registerBidAdapter(PubmaticAdapter(), 'pubmatic'); -this.registerBidAdapter(YieldbotAdapter(), 'yieldbot'); -this.registerBidAdapter(IndexExchange(), 'indexExchange'); -this.registerBidAdapter(SpringServeAdapter(), 'springserve'); -this.registerBidAdapter(Sovrn(),'sovrn'); -this.registerBidAdapter(AolAdapter(), 'aol'); -this.registerBidAdapter(PulsePointAdapter(),'pulsepoint'); +exports.registerBidAdapter(new RubiconAdapter(), 'rubicon'); +exports.registerBidAdapter(new AppNexusAdapter.createNew(), 'appnexus'); +exports.registerBidAdapter(new OpenxAdapter(), 'openx'); +exports.registerBidAdapter(new PubmaticAdapter(), 'pubmatic'); +exports.registerBidAdapter(new YieldbotAdapter(), 'yieldbot'); +exports.registerBidAdapter(new IndexExchange(), 'indexExchange'); +exports.registerBidAdapter(new SpringServeAdapter(), 'springserve'); +exports.registerBidAdapter(new Sovrn(), 'sovrn'); +exports.registerBidAdapter(new AolAdapter(), 'aol'); +exports.registerBidAdapter(new PulsePointAdapter(), 'pulsepoint'); + //default bidder alias -this.aliasBidAdapter('appnexus', 'brealtime'); -this.registerBidAdapter(AdformAdapter(), 'adform'); +exports.aliasBidAdapter('appnexus', 'brealtime'); +exports.registerBidAdapter(new AdformAdapter(), 'adform'); diff --git a/src/adapters/adapter.js b/src/adapters/adapter.js index 200f3cf1509..30c6dc10bb5 100644 --- a/src/adapters/adapter.js +++ b/src/adapters/adapter.js @@ -1,24 +1,24 @@ -function Adapter(code){ - var bidderCode = code; +function Adapter(code) { + var bidderCode = code; - function setBidderCode(code){ - bidderCode = code; - } + function setBidderCode(code) { + bidderCode = code; + } - function getBidderCode(){ - return bidderCode; - } + function getBidderCode() { + return bidderCode; + } - function callBids(){ + function callBids() { } - return { - callBids: callBids, - setBidderCode: setBidderCode, - getBidderCode: getBidderCode - }; + return { + callBids: callBids, + setBidderCode: setBidderCode, + getBidderCode: getBidderCode + }; } -exports.createNew = function(bidderCode){ - return new Adapter(bidderCode); -}; \ No newline at end of file +exports.createNew = function (bidderCode) { + return new Adapter(bidderCode); +}; diff --git a/src/adapters/adform.js b/src/adapters/adform.js index a346dc3fdbf..dc9bbfa9bc8 100644 --- a/src/adapters/adform.js +++ b/src/adapters/adform.js @@ -5,145 +5,159 @@ var bidfactory = require('../bidfactory.js'); function AdformAdapter() { - return { - callBids: _callBids - }; - - function _callBids(params) { - var callbackName = '_adf_' + utils.getUniqueIdentifierStr(), bid, noDomain = true; - var bids = params.bids; - var request = []; - - for (var i = 0, l = bids.length; i < l; i++) { - bid = bids[i]; - if (bid.adxDomain && noDomain) { - noDomain = false; - request.unshift('//' + bid.adxDomain + '/adx/?rp=4'); - } - request.push(formRequestUrl(bid.params)); - } - - if (noDomain) { - request.unshift('//adx.adform.net/adx/?rp=4'); - } - - pbjs[callbackName] = handleCallback(bids); - request.push('callback=pbjs.' + callbackName); + return { + callBids: _callBids + }; + + function _callBids(params) { + var callbackName = '_adf_' + utils.getUniqueIdentifierStr(); + var bid; + var noDomain = true; + var bids = params.bids; + var request = []; + + for (var i = 0, l = bids.length; i < l; i++) { + bid = bids[i]; + if (bid.adxDomain && noDomain) { + noDomain = false; + request.unshift('//' + bid.adxDomain + '/adx/?rp=4'); + } + + request.push(formRequestUrl(bid.params)); + } - adloader.loadScript(request.join('&')); + if (noDomain) { + request.unshift('//adx.adform.net/adx/?rp=4'); } - function formRequestUrl(reqData) { - var key; - var url = []; + pbjs[callbackName] = handleCallback(bids); + request.push('callback=pbjs.' + callbackName); - var validProps = [ - 'mid', 'inv', 'pdom', 'mname', 'mkw', 'mkv', 'cat', 'bcat', 'bcatrt', 'adv', 'advt', 'cntr', 'cntrt', 'maxp', - 'minp', 'sminp', 'w', 'h', 'pb', 'pos', 'cturl', 'iturl', 'cttype', 'hidedomain', 'cdims', 'test' - ]; + adloader.loadScript(request.join('&')); + } - for (var i = 0, l = validProps.length; i < l; i++) { - key = validProps[i]; - if (reqData.hasOwnProperty(key)) - url.push(key, '=', reqData[key], '&'); - } + function formRequestUrl(reqData) { + var key; + var url = []; + + var validProps = [ + 'mid', 'inv', 'pdom', 'mname', 'mkw', 'mkv', 'cat', 'bcat', 'bcatrt', 'adv', 'advt', 'cntr', 'cntrt', 'maxp', + 'minp', 'sminp', 'w', 'h', 'pb', 'pos', 'cturl', 'iturl', 'cttype', 'hidedomain', 'cdims', 'test' + ]; - return encode64(url.join('')); + for (var i = 0, l = validProps.length; i < l; i++) { + key = validProps[i]; + if (reqData.hasOwnProperty(key)) + url.push(key, '=', reqData[key], '&'); } - function handleCallback(bids) { - return function handleResponse(adItems) { - var bidObject, bidder = 'adform', adItem, bid; - for(var i = 0, l = adItems.length; i < l; i++){ - adItem = adItems[i]; - bid = bids[i]; - if (adItem && adItem.response === 'banner' && - verifySize(adItem, bid.sizes)) { - - bidObject = bidfactory.createBid(1); - bidObject.bidderCode = bidder; - bidObject.cpm = adItem.win_bid; - bidObject.cur = adItem.win_cur; - bidObject.ad = adItem.banner; - bidObject.width = adItem.width; - bidObject.height = adItem.height; - bidmanager.addBidResponse(bid.placementCode, bidObject); - } else { - bidObject = bidfactory.createBid(2); - bidObject.bidderCode = bidder; - bidmanager.addBidResponse(bid.placementCode, bidObject); - } - } - }; - - function verifySize(adItem, validSizes) { - for (var j = 0, k = validSizes.length; j < k; j++) { - if (adItem.width === validSizes[j][0] && - adItem.height === validSizes[j][1]) { - return true; - } - } - return false; + return encode64(url.join('')); + } + + function handleCallback(bids) { + return function handleResponse(adItems) { + var bidObject; + var bidder = 'adform'; + var adItem; + var bid; + for (var i = 0, l = adItems.length; i < l; i++) { + adItem = adItems[i]; + bid = bids[i]; + if (adItem && adItem.response === 'banner' && + verifySize(adItem, bid.sizes)) { + + bidObject = bidfactory.createBid(1); + bidObject.bidderCode = bidder; + bidObject.cpm = adItem.win_bid; + bidObject.cur = adItem.win_cur; + bidObject.ad = adItem.banner; + bidObject.width = adItem.width; + bidObject.height = adItem.height; + bidmanager.addBidResponse(bid.placementCode, bidObject); + } else { + bidObject = bidfactory.createBid(2); + bidObject.bidderCode = bidder; + bidmanager.addBidResponse(bid.placementCode, bidObject); } - } + } + }; - function encode64(input) { - var out = []; - var chr1, chr2, chr3, enc1, enc2, enc3, enc4; - var i = 0; - var _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_="; - - input = utf8_encode(input); - - while (i < input.length) { - - chr1 = input.charCodeAt(i++); - chr2 = input.charCodeAt(i++); - chr3 = input.charCodeAt(i++); - - enc1 = chr1 >> 2; - enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); - enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); - enc4 = chr3 & 63; - - if (isNaN(chr2)) { - enc3 = enc4 = 64; - } else if (isNaN(chr3)) { - enc4 = 64; - } - out.push(_keyStr.charAt(enc1), _keyStr.charAt(enc2)); - if (enc3 != 64) - out.push(_keyStr.charAt(enc3)); - if (enc4 != 64) - out.push(_keyStr.charAt(enc4)); + function verifySize(adItem, validSizes) { + for (var j = 0, k = validSizes.length; j < k; j++) { + if (adItem.width === validSizes[j][0] && + adItem.height === validSizes[j][1]) { + return true; } + } - return out.join(''); + return false; + } + } + + function encode64(input) { + var out = []; + var chr1; + var chr2; + var chr3; + var enc1; + var enc2; + var enc3; + var enc4; + var i = 0; + var _keyStr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_='; + + input = utf8_encode(input); + + while (i < input.length) { + + chr1 = input.charCodeAt(i++); + chr2 = input.charCodeAt(i++); + chr3 = input.charCodeAt(i++); + + enc1 = chr1 >> 2; + enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); + enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); + enc4 = chr3 & 63; + + if (isNaN(chr2)) { + enc3 = enc4 = 64; + } else if (isNaN(chr3)) { + enc4 = 64; + } + + out.push(_keyStr.charAt(enc1), _keyStr.charAt(enc2)); + if (enc3 !== 64) + out.push(_keyStr.charAt(enc3)); + if (enc4 !== 64) + out.push(_keyStr.charAt(enc4)); } - function utf8_encode(string) { - string = string.replace(/\r\n/g, "\n"); - var utftext = ""; + return out.join(''); + } - for (var n = 0; n < string.length; n++) { + function utf8_encode(string) { + string = string.replace(/\r\n/g, '\n'); + var utftext = ''; - var c = string.charCodeAt(n); + for (var n = 0; n < string.length; n++) { - if (c < 128) { - utftext += String.fromCharCode(c); - } else if ((c > 127) && (c < 2048)) { - utftext += String.fromCharCode((c >> 6) | 192); - utftext += String.fromCharCode((c & 63) | 128); - } else { - utftext += String.fromCharCode((c >> 12) | 224); - utftext += String.fromCharCode(((c >> 6) & 63) | 128); - utftext += String.fromCharCode((c & 63) | 128); - } - } + var c = string.charCodeAt(n); - return utftext; + if (c < 128) { + utftext += String.fromCharCode(c); + } else if ((c > 127) && (c < 2048)) { + utftext += String.fromCharCode((c >> 6) | 192); + utftext += String.fromCharCode((c & 63) | 128); + } else { + utftext += String.fromCharCode((c >> 12) | 224); + utftext += String.fromCharCode(((c >> 6) & 63) | 128); + utftext += String.fromCharCode((c & 63) | 128); + } } + return utftext; + } + } module.exports = AdformAdapter; diff --git a/src/adapters/aol.js b/src/adapters/aol.js index 710e5a136c8..2de71219d6a 100644 --- a/src/adapters/aol.js +++ b/src/adapters/aol.js @@ -1,184 +1,185 @@ -var utils = require('../utils.js'), - bidfactory = require('../bidfactory.js'), - bidmanager = require('../bidmanager.js'), - adloader = require('../adloader'); +var utils = require('../utils.js'); +var bidfactory = require('../bidfactory.js'); +var bidmanager = require('../bidmanager.js'); +var adloader = require('../adloader'); var AolAdapter = function AolAdapter() { - // constants - var ADTECH_PLACEMENT_RXP = /\W/g, - ADTECH_URI = (window.location.protocol) + '//aka-cdn.adtechus.com/dt/common/DAC.js', - ADTECH_BIDDER_NAME = 'aol', - ADTECH_PUBAPI_CONFIG = { - pixelsDivId: 'pixelsDiv', - defaultKey: 'aolBid', - roundingConfig: [{ - from: 0, - to: 999, - roundFunction: 'tenCentsRound' - }, { - from: 1000, - to: -1, - roundValue: 1000 - }], - pubApiOK: _addBid, - pubApiER: _addErrorBid - }; - - var bids, - bidsMap = {}, - d = window.document, - h = d.getElementsByTagName('HEAD')[0], - aliasCount = 0, - dummyUnitIdCount = 0; - - /** - * @private Given a placementCode slot path/div id - * for a unit, return a unique alias - * @param {String} placementCode - * @return {String} alias - */ - function _generateAlias(placementCode) { - return (placementCode || 'alias').replace(ADTECH_PLACEMENT_RXP, '') + (++aliasCount); - } - - /** - * @private create a div that we'll use as the - * location for the AOL unit; AOL will document.write - * if the div is not present in the document. - * @param {String} id to identify the div - * @return {String} the id used with the div - */ - function _dummyUnit(id) { - var div = d.createElement('DIV'); - - if (!id || !id.length) { - id = 'ad-placeholder-' + (++dummyUnitIdCount); - } - - div.id = id + '-head-unit'; - h.appendChild(div); - return div.id; - } - - /** - * @private Add a succesful bid response for aol - * @param {ADTECHResponse} response the response for the bid - * @param {ADTECHContext} context the context passed from aol - */ - function _addBid(response, context) { - var bid = bidsMap[context.placement], - cpm; - - if (!bid) { - utils.logError('mismatched bid: ' + context.placement, ADTECH_BIDDER_NAME, context); - return; - } - - cpm = response.getCPM(); - if (cpm === null || isNaN(cpm)) { - return _addErrorBid(response, context); - } - - var bidResponse = bidfactory.createBid(1); - bidResponse.bidderCode = ADTECH_BIDDER_NAME; - bidResponse.ad = response.getCreative() + response.getPixels(); - bidResponse.cpm = cpm; - bidResponse.width = response.getAdWidth(); - bidResponse.height = response.getAdHeight(); - bidResponse.creativeId = response.getCreativeId(); - - // add it to the bid manager - bidmanager.addBidResponse(bid.placementCode, bidResponse); - } - - /** - * @private Add an error bid response for aol - * @param {ADTECHResponse} response the response for the bid - * @param {ADTECHContext} context the context passed from aol - */ - function _addErrorBid(response, context) { - var bid = bidsMap[context.alias || context.placement]; - - if (!bid) { - utils.logError('mismatched bid: ' + context.placement, ADTECH_BIDDER_NAME, context); - return; - } - - var bidResponse = bidfactory.createBid(2); - bidResponse.bidderCode = ADTECH_BIDDER_NAME; - bidResponse.reason = response.getNbr(); - bidResponse.raw = response.getResponse(); - bidmanager.addBidResponse(bid.placementCode, bidResponse); - } - - - /** - * @private map a prebid bidrequest to an ADTECH/aol bid request - * @param {Bid} bid the bid request - * @return {Object} the bid request, formatted for the ADTECH/DAC api - */ - function _mapUnit(bid) { - // save the bid - bidsMap[bid.params.placement] = bid; - - return { - adContainerId: _dummyUnit(bid.params.adContainerId), - server: bid.params.server, // By default, DAC.js will use the US region endpoint (adserver.adtechus.com) - sizeid: bid.params.sizeId || 0, - pageid: bid.params.pageId, - secure: false, - serviceType: 'pubapi', - performScreenDetection: false, - alias: bid.params.alias || _generateAlias(bid.placementCode), - network: bid.params.network, - placement: parseInt(bid.params.placement), - gpt: { - adUnitPath: bid.params.adUnitPath || bid.placementCode, - size: bid.params.size || (bid.sizes || [])[0] - }, - params: { - cors: 'yes', - cmd: 'bid' - }, - pubApiConfig: ADTECH_PUBAPI_CONFIG, - placementCode: bid.placementCode - }; - } - - /** - * @private once ADTECH is loaded, request bids by - * calling ADTECH.loadAd - */ - function _reqBids() { - if (!window.ADTECH) { - utils.logError('window.ADTECH is not present!', ADTECH_BIDDER_NAME); - return; - } - - // get the bids - utils._each(bids, function(bid) { - var bidreq = _mapUnit(bid); - window.ADTECH.loadAd(bidreq); - }); - } - - /** - * @public call the bids - * this requests the specified bids - * from aol marketplace - * @param {Object} params - * @param {Array} params.bids the bids to be requested - */ - function _callBids(params) { - bids = params.bids; - if (!bids || !bids.length) return; - adloader.loadScript(ADTECH_URI, _reqBids); - } - - return { - callBids: _callBids - }; + // constants + var ADTECH_PLACEMENT_RXP = /\W/g; + var ADTECH_URI = (window.location.protocol) + '//aka-cdn.adtechus.com/dt/common/DAC.js'; + var ADTECH_BIDDER_NAME = 'aol'; + var ADTECH_PUBAPI_CONFIG = { + pixelsDivId: 'pixelsDiv', + defaultKey: 'aolBid', + roundingConfig: [ + { + from: 0, + to: 999, + roundFunction: 'tenCentsRound' + }, { + from: 1000, + to: -1, + roundValue: 1000 + } + ], + pubApiOK: _addBid, + pubApiER: _addErrorBid + }; + + var bids; + var bidsMap = {}; + var d = window.document; + var h = d.getElementsByTagName('HEAD')[0]; + var aliasCount = 0; + var dummyUnitIdCount = 0; + + /** + * @private Given a placementCode slot path/div id + * for a unit, return a unique alias + * @param {String} placementCode + * @return {String} alias + */ + function _generateAlias(placementCode) { + return (placementCode || 'alias').replace(ADTECH_PLACEMENT_RXP, '') + (++aliasCount); + } + + /** + * @private create a div that we'll use as the + * location for the AOL unit; AOL will document.write + * if the div is not present in the document. + * @param {String} id to identify the div + * @return {String} the id used with the div + */ + function _dummyUnit(id) { + var div = d.createElement('DIV'); + + if (!id || !id.length) { + id = 'ad-placeholder-' + (++dummyUnitIdCount); + } + + div.id = id + '-head-unit'; + h.appendChild(div); + return div.id; + } + + /** + * @private Add a succesful bid response for aol + * @param {ADTECHResponse} response the response for the bid + * @param {ADTECHContext} context the context passed from aol + */ + function _addBid(response, context) { + var bid = bidsMap[context.placement]; + var cpm; + + if (!bid) { + utils.logError('mismatched bid: ' + context.placement, ADTECH_BIDDER_NAME, context); + return; + } + + cpm = response.getCPM(); + if (cpm === null || isNaN(cpm)) { + return _addErrorBid(response, context); + } + + var bidResponse = bidfactory.createBid(1); + bidResponse.bidderCode = ADTECH_BIDDER_NAME; + bidResponse.ad = response.getCreative() + response.getPixels(); + bidResponse.cpm = cpm; + bidResponse.width = response.getAdWidth(); + bidResponse.height = response.getAdHeight(); + bidResponse.creativeId = response.getCreativeId(); + + // add it to the bid manager + bidmanager.addBidResponse(bid.placementCode, bidResponse); + } + + /** + * @private Add an error bid response for aol + * @param {ADTECHResponse} response the response for the bid + * @param {ADTECHContext} context the context passed from aol + */ + function _addErrorBid(response, context) { + var bid = bidsMap[context.alias || context.placement]; + + if (!bid) { + utils.logError('mismatched bid: ' + context.placement, ADTECH_BIDDER_NAME, context); + return; + } + + var bidResponse = bidfactory.createBid(2); + bidResponse.bidderCode = ADTECH_BIDDER_NAME; + bidResponse.reason = response.getNbr(); + bidResponse.raw = response.getResponse(); + bidmanager.addBidResponse(bid.placementCode, bidResponse); + } + + /** + * @private map a prebid bidrequest to an ADTECH/aol bid request + * @param {Bid} bid the bid request + * @return {Object} the bid request, formatted for the ADTECH/DAC api + */ + function _mapUnit(bid) { + // save the bid + bidsMap[bid.params.placement] = bid; + + return { + adContainerId: _dummyUnit(bid.params.adContainerId), + server: bid.params.server, // By default, DAC.js will use the US region endpoint (adserver.adtechus.com) + sizeid: bid.params.sizeId || 0, + pageid: bid.params.pageId, + secure: false, + serviceType: 'pubapi', + performScreenDetection: false, + alias: bid.params.alias || _generateAlias(bid.placementCode), + network: bid.params.network, + placement: parseInt(bid.params.placement), + gpt: { + adUnitPath: bid.params.adUnitPath || bid.placementCode, + size: bid.params.size || (bid.sizes || [])[0] + }, + params: { + cors: 'yes', + cmd: 'bid' + }, + pubApiConfig: ADTECH_PUBAPI_CONFIG, + placementCode: bid.placementCode + }; + } + + /** + * @private once ADTECH is loaded, request bids by + * calling ADTECH.loadAd + */ + function _reqBids() { + if (!window.ADTECH) { + utils.logError('window.ADTECH is not present!', ADTECH_BIDDER_NAME); + return; + } + + // get the bids + utils._each(bids, function (bid) { + var bidreq = _mapUnit(bid); + window.ADTECH.loadAd(bidreq); + }); + } + + /** + * @public call the bids + * this requests the specified bids + * from aol marketplace + * @param {Object} params + * @param {Array} params.bids the bids to be requested + */ + function _callBids(params) { + bids = params.bids; + if (!bids || !bids.length) return; + adloader.loadScript(ADTECH_URI, _reqBids); + } + + return { + callBids: _callBids + }; }; -module.exports = AolAdapter; \ No newline at end of file +module.exports = AolAdapter; diff --git a/src/adapters/appnexus.js b/src/adapters/appnexus.js index 7a49baa7221..b07a16b817a 100644 --- a/src/adapters/appnexus.js +++ b/src/adapters/appnexus.js @@ -5,216 +5,221 @@ var bidmanager = require('../bidmanager.js'); var bidfactory = require('../bidfactory.js'); var Adapter = require('./adapter.js'); -var AppNexusAdapter = function AppNexusAdapter() { - var baseAdapter = Adapter.createNew('appnexus'); - - baseAdapter.callBids = function(params){ - var bidCode = baseAdapter.getBidderCode(); - - var anArr = params.bids; - var bidsCount = anArr.length; - - //set expected bids count for callback execution - bidmanager.setExpectedBidsCount(bidCode,bidsCount); - - for (var i = 0; i < bidsCount; i++) { - var bidRequest = anArr[i]; - var callbackId = utils.getUniqueIdentifierStr(); - adloader.loadScript(buildJPTCall(bidRequest, callbackId)); - //store a reference to the bidRequest from the callback id - bidmanager.pbCallbackMap[callbackId] = bidRequest; - } - }; - - - function buildJPTCall(bid, callbackId) { - - //determine tag params - var placementId = utils.getBidIdParamater('placementId', bid.params); - //memberId will be deprecated, use member instead - var memberId = utils.getBidIdParamater('memberId', bid.params); - var member = utils.getBidIdParamater('member', bid.params); - var inventoryCode = utils.getBidIdParamater('invCode', bid.params); - var query = utils.getBidIdParamater('query', bid.params); - var referrer = utils.getBidIdParamater('referrer', bid.params); - var altReferrer = utils.getBidIdParamater('alt_referrer', bid.params); - - //build our base tag, based on if we are http or https - - var jptCall = 'http' + ('https:' === document.location.protocol ? 's://secure.adnxs.com/jpt?' : '://ib.adnxs.com/jpt?'); - - jptCall = utils.tryAppendQueryString(jptCall, 'callback', 'pbjs.handleAnCB'); - jptCall = utils.tryAppendQueryString(jptCall, 'callback_uid', callbackId); - jptCall = utils.tryAppendQueryString(jptCall, 'psa', '0'); - jptCall = utils.tryAppendQueryString(jptCall, 'id', placementId); - if(member){ - jptCall = utils.tryAppendQueryString(jptCall, 'member_id', member); - }else if(memberId){ - jptCall = utils.tryAppendQueryString(jptCall, 'member_id', memberId); - utils.logMessage('appnexus.callBids: "memberId" will be deprecated soon. Please use "member" instead'); - } - jptCall = utils.tryAppendQueryString(jptCall, 'code', inventoryCode); - jptCall = utils.tryAppendQueryString(jptCall, 'code', inventoryCode); - - - - //sizes takes a bit more logic - var sizeQueryString = ''; - var parsedSizes = utils.parseSizesInput(bid.sizes); - - //combine string into proper querystring for impbus - var 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 (var 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 + '&'; - } - - //this will be deprecated soon - var targetingParams = utils.parseQueryStringParameters(query); - - if (targetingParams) { - //don't append a & here, we have already done it in parseQueryStringParameters - jptCall += targetingParams; - } - - //append custom attributes: - var paramsCopy = utils.extend({}, bid.params); - //delete attributes already used - delete paramsCopy.placementId; - delete paramsCopy.memberId; - delete paramsCopy.invCode; - delete paramsCopy.query; - delete paramsCopy.referrer; - delete paramsCopy.alt_referrer; - delete paramsCopy.member; - - //get the reminder - var 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); - } - - // @if NODE_ENV='debug' - utils.logMessage('jpt request built: ' + jptCall); - // @endif - - //append a timer here to track latency - bid.startTime = new Date().getTime(); - - return jptCall; - - } - - //expose the callback to the global object: - pbjs.handleAnCB = function(jptResponseObj) { - - var bidCode; - - if (jptResponseObj && jptResponseObj.callback_uid) { - - var error; - var responseCPM; - var id = jptResponseObj.callback_uid, - placementCode = '', - //retrieve bid object by callback ID - bidObj = bidmanager.getPlacementIdByCBIdentifer(id); - if (bidObj) { - - bidCode = bidObj.bidder; - - placementCode = bidObj.placementCode; - //set the status - bidObj.status = CONSTANTS.STATUS.GOOD; - } - - // @if NODE_ENV='debug' - utils.logMessage('JSONP callback function called for ad ID: ' + id); - // @endif - var bid = []; - if (jptResponseObj.result && jptResponseObj.result.cpm && jptResponseObj.result.cpm !== 0) { - responseCPM = parseInt(jptResponseObj.result.cpm, 10); - - //CPM response from /jpt is dollar/cent multiplied by 10000 - //in order to avoid using floats - //switch CPM to "dollar/cent" - responseCPM = responseCPM / 10000; - var responseAd = jptResponseObj.result.ad; - //store bid response - //bid status is good (indicating 1) - var adId = jptResponseObj.result.creative_id; - bid = bidfactory.createBid(1); - 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 = jptResponseObj.result.deal_id; - - bidmanager.addBidResponse(placementCode, bid); - - - } else { - //no response data - // @if NODE_ENV='debug' - utils.logMessage('No prebid response from AppNexus for placement code ' + placementCode); - // @endif - //indicate that there is no bid for this placement - bid = bidfactory.createBid(2); - bid.bidderCode = bidCode; - bidmanager.addBidResponse(placementCode, bid); - } - - - - } else { - //no response data - // @if NODE_ENV='debug' - utils.logMessage('No prebid response for placement %%PLACEMENT%%'); - // @endif - - } - - }; - - return { - callBids: baseAdapter.callBids, - setBidderCode: baseAdapter.setBidderCode, - createNew: exports.createNew, - buildJPTCall : buildJPTCall - }; +var AppNexusAdapter; +AppNexusAdapter = function AppNexusAdapter() { + var baseAdapter = Adapter.createNew('appnexus'); + + baseAdapter.callBids = function (params) { + var bidCode = baseAdapter.getBidderCode(); + + var anArr = params.bids; + var bidsCount = anArr.length; + + //set expected bids count for callback execution + bidmanager.setExpectedBidsCount(bidCode, bidsCount); + + for (var i = 0; i < bidsCount; i++) { + var bidRequest = anArr[i]; + var callbackId = utils.getUniqueIdentifierStr(); + adloader.loadScript(buildJPTCall(bidRequest, callbackId)); + + //store a reference to the bidRequest from the callback id + bidmanager.pbCallbackMap[callbackId] = bidRequest; + } + }; + + function buildJPTCall(bid, callbackId) { + + //determine tag params + var placementId = utils.getBidIdParamater('placementId', bid.params); + + //memberId will be deprecated, use member instead + var memberId = utils.getBidIdParamater('memberId', bid.params); + var member = utils.getBidIdParamater('member', bid.params); + var inventoryCode = utils.getBidIdParamater('invCode', bid.params); + var query = utils.getBidIdParamater('query', bid.params); + var referrer = utils.getBidIdParamater('referrer', bid.params); + var altReferrer = utils.getBidIdParamater('alt_referrer', bid.params); + + //build our base tag, based on if we are http or https + + var jptCall = 'http' + (document.location.protocol === 'https:' ? 's://secure.adnxs.com/jpt?' : '://ib.adnxs.com/jpt?'); + + jptCall = utils.tryAppendQueryString(jptCall, 'callback', 'pbjs.handleAnCB'); + jptCall = utils.tryAppendQueryString(jptCall, 'callback_uid', callbackId); + jptCall = utils.tryAppendQueryString(jptCall, 'psa', '0'); + jptCall = utils.tryAppendQueryString(jptCall, 'id', placementId); + if (member) { + jptCall = utils.tryAppendQueryString(jptCall, 'member_id', member); + } else if (memberId) { + jptCall = utils.tryAppendQueryString(jptCall, 'member_id', memberId); + utils.logMessage('appnexus.callBids: "memberId" will be deprecated soon. Please use "member" instead'); + } + + jptCall = utils.tryAppendQueryString(jptCall, 'code', inventoryCode); + jptCall = utils.tryAppendQueryString(jptCall, 'code', inventoryCode); + + //sizes takes a bit more logic + var sizeQueryString = ''; + var parsedSizes = utils.parseSizesInput(bid.sizes); + + //combine string into proper querystring for impbus + var 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 (var 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 + '&'; + } + + //this will be deprecated soon + var targetingParams = utils.parseQueryStringParameters(query); + + if (targetingParams) { + //don't append a & here, we have already done it in parseQueryStringParameters + jptCall += targetingParams; + } + + //append custom attributes: + var paramsCopy = utils.extend({}, bid.params); + + //delete attributes already used + delete paramsCopy.placementId; + delete paramsCopy.memberId; + delete paramsCopy.invCode; + delete paramsCopy.query; + delete paramsCopy.referrer; + delete paramsCopy.alt_referrer; + delete paramsCopy.member; + + //get the reminder + var 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); + } + + // @if NODE_ENV='debug' + utils.logMessage('jpt request built: ' + jptCall); + + // @endif + + //append a timer here to track latency + bid.startTime = new Date().getTime(); + + return jptCall; + + } + + //expose the callback to the global object: + pbjs.handleAnCB = function (jptResponseObj) { + + var bidCode; + + if (jptResponseObj && jptResponseObj.callback_uid) { + + var responseCPM; + var id = jptResponseObj.callback_uid; + var placementCode = ''; + var bidObj = bidmanager.getPlacementIdByCBIdentifer(id); + if (bidObj) { + + bidCode = bidObj.bidder; + + placementCode = bidObj.placementCode; + + //set the status + bidObj.status = CONSTANTS.STATUS.GOOD; + } + + // @if NODE_ENV='debug' + utils.logMessage('JSONP callback function called for ad ID: ' + id); + + // @endif + var bid = []; + if (jptResponseObj.result && jptResponseObj.result.cpm && jptResponseObj.result.cpm !== 0) { + responseCPM = parseInt(jptResponseObj.result.cpm, 10); + + //CPM response from /jpt is dollar/cent multiplied by 10000 + //in order to avoid using floats + //switch CPM to "dollar/cent" + responseCPM = responseCPM / 10000; + + //store bid response + //bid status is good (indicating 1) + var adId = jptResponseObj.result.creative_id; + bid = bidfactory.createBid(1); + 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 = jptResponseObj.result.deal_id; + + bidmanager.addBidResponse(placementCode, bid); + + } else { + //no response data + // @if NODE_ENV='debug' + utils.logMessage('No prebid response from AppNexus for placement code ' + placementCode); + + // @endif + //indicate that there is no bid for this placement + bid = bidfactory.createBid(2); + bid.bidderCode = bidCode; + bidmanager.addBidResponse(placementCode, bid); + } + + } else { + //no response data + // @if NODE_ENV='debug' + utils.logMessage('No prebid response for placement %%PLACEMENT%%'); + + // @endif + + } + + }; + + return { + callBids: baseAdapter.callBids, + setBidderCode: baseAdapter.setBidderCode, + createNew: exports.createNew, + buildJPTCall: buildJPTCall + }; }; -exports.createNew = function(){ - return new AppNexusAdapter(); +exports.createNew = function () { + return new AppNexusAdapter(); }; + // module.exports = AppNexusAdapter; diff --git a/src/adapters/indexExchange.js b/src/adapters/indexExchange.js index 6cc9d129229..ad419d8ed04 100644 --- a/src/adapters/indexExchange.js +++ b/src/adapters/indexExchange.js @@ -1,5 +1,5 @@ //Factory for creating the bidderAdaptor -var CONSTANTS = require('../constants.json'); +// jshint ignore:start var utils = require('../utils.js'); var bidfactory = require('../bidfactory.js'); var bidmanager = require('../bidmanager.js'); @@ -9,363 +9,402 @@ var ADAPTER_NAME = 'INDEXEXCHANGE'; var ADAPTER_CODE = 'indexExchange'; var cygnus_index_primary_request = true; -var cygnus_index_parse_res = function() {}; +var cygnus_index_parse_res = function () { +}; + window.cygnus_index_args = {}; -var cygnus_index_adunits = [[728,90],[120,600],[300,250],[160,600],[336,280],[234,60],[300,600],[300,50],[320,50],[970,250],[300,1050],[970,90],[180,150]]; - -var cygnus_index_start = function() { - cygnus_index_args.parseFn = cygnus_index_parse_res; - var escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; - var meta = { - '\b': '\\b', - '\t': '\\t', - '\n': '\\n', - '\f': '\\f', - '\r': '\\r', - '"': '\\"', - '\\': '\\\\' - }; - - function escapeCharacter(character) { - var escaped = meta[character]; - if (typeof escaped === 'string') { - return escaped; - } else { - return '\\u' + ('0000' + character.charCodeAt(0).toString(16)).slice(-4); - } - } - - function quote(string) { - escapable.lastIndex = 0; - if (escapable.test(string)) { - return string.replace(escapable, escapeCharacter); - } else { - return string; - } - } - - function OpenRTBRequest(siteID, parseFn, timeoutDelay) { - this.initialized = false; - if (typeof siteID !== "number" || siteID % 1 !== 0 || siteID < 0) { - throw "Invalid Site ID"; - } - if (typeof timeoutDelay === "number" && timeoutDelay % 1 === 0 && timeoutDelay >= 0) { - this.timeoutDelay = timeoutDelay; - } - - this.siteID = siteID; - this.impressions = []; - this._parseFnName = undefined; - if (top === self) { - this.sitePage = location.href; - this.topframe = 1; - } else { - this.sitePage = document.referrer; - this.topframe = 0; - } - if (typeof parseFn !== 'undefined') { - if (typeof parseFn === 'function') { - this._parseFnName = "cygnus_index_args.parseFn"; - } else { - throw "Invalid jsonp target function"; - } - } - if (typeof _IndexRequestData.requestCounter === 'undefined') { - _IndexRequestData.requestCounter = Math.floor(Math.random() * 256); - } else { - _IndexRequestData.requestCounter = (_IndexRequestData.requestCounter + 1) % 256; - } - this.requestID = String((new Date().getTime() % 2592000) * 256 + _IndexRequestData.requestCounter + 256); - this.initialized = true; - } - OpenRTBRequest.prototype.serialize = function() { - var json = '{"id":' + this.requestID + ',"site":{"page":"' + quote(this.sitePage) + '"'; - if (typeof document.referrer === 'string') { - json += ',"ref":"' + quote(document.referrer) + '"'; - } - json += '},"imp":['; - for (var i = 0; i < this.impressions.length; i++) { - var impObj = this.impressions[i]; - var ext = []; - json += '{"id":"' + impObj.id + '", "banner":{"w":' + impObj.w + ',"h":' + impObj.h + ',"topframe":' + String(this.topframe) + "}"; - if (typeof impObj.bidfloor === 'number') { - json += ',"bidfloor":' + impObj.bidfloor; - if (typeof impObj.bidfloorcur === 'string') { - json += ',"bidfloorcur":"' + quote(impObj.bidfloorcur) + '"'; - } - } - if (typeof impObj.slotID === 'string' && (!impObj.slotID.match(/^\s*$/))) { - ext.push('"sid":"' + quote(impObj.slotID) + '"'); - } - if (typeof impObj.siteID === 'number') { - ext.push('"siteID":' + impObj.siteID); - } - if (ext.length > 0) { - json += ',"ext": {' + ext.join() + '}'; - } - if (i + 1 == this.impressions.length) { - json += '}'; - } else { - json += '},'; - } - } - json += "]}"; - return json; - }; - OpenRTBRequest.prototype.setPageOverride = function(sitePageOverride) { - if (typeof sitePageOverride === 'string' && (!sitePageOverride.match(/^\s*$/))) { - this.sitePage = sitePageOverride; - return true; - } else { - return false; - } - }; - OpenRTBRequest.prototype.addImpression = function(width, height, bidFloor, bidFloorCurrency, slotID, siteID) { - var impObj = { - 'id': String(this.impressions.length + 1) - }; - if (typeof width !== 'number' || width <= 1) { - return null; - } - if (typeof height !== 'number' || height <= 1) { - return null; - } - if ((typeof slotID === 'string' || typeof slotID === 'number') && String(slotID).length <= 50) { - impObj.slotID = String(slotID); - } - impObj.w = width; - impObj.h = height; - if (bidFloor !== undefined && typeof bidFloor !== 'number') { - return null; - } - if (typeof bidFloor === 'number') { - if (bidFloor < 0) { - return null; - } - impObj.bidfloor = bidFloor; - if (bidFloorCurrency !== undefined && typeof bidFloorCurrency !== 'string') { - return null; - } - impObj.bidfloorcur = bidFloorCurrency; - } - if (typeof siteID !== 'undefined') { - if (typeof siteID === 'number' && siteID % 1 === 0 && siteID >= 0) { - impObj.siteID = siteID; - } else { - return null; - } - } - this.impressions.push(impObj); - return impObj.id; - }; - - OpenRTBRequest.prototype.buildRequest = function() { - if (this.impressions.length === 0 || this.initialized !== true) { - return; - } - var jsonURI = encodeURIComponent(this.serialize()); - var scriptSrc = window.location.protocol === 'https:' ? 'https://as-sec.casalemedia.com' : 'http://as.casalemedia.com'; - scriptSrc += '/headertag?v=9&x3=1&fn=cygnus_index_parse_res&s=' + this.siteID + '&r=' + jsonURI; - if (typeof this.timeoutDelay === "number" && this.timeoutDelay % 1 === 0 && this.timeoutDelay >= 0) { - scriptSrc += '&t=' + this.timeoutDelay; - } - return scriptSrc; - }; - try { - if (typeof cygnus_index_args === 'undefined' || typeof cygnus_index_args.siteID === 'undefined' || typeof cygnus_index_args.slots === 'undefined') { - return; - } - if (typeof _IndexRequestData === 'undefined') { - _IndexRequestData = {}; - _IndexRequestData.impIDToSlotID = {}; - _IndexRequestData.reqOptions = {}; - } - var req = new OpenRTBRequest(cygnus_index_args.siteID, cygnus_index_args.parseFn, cygnus_index_args.timeout); - if (cygnus_index_args.url && typeof cygnus_index_args.url === 'string') { - req.setPageOverride(cygnus_index_args.url); - } - _IndexRequestData.impIDToSlotID[req.requestID] = {}; - _IndexRequestData.reqOptions[req.requestID] = {}; - var slotDef, impID; - - for (var i = 0; i < cygnus_index_args.slots.length; i++) { - slotDef = cygnus_index_args.slots[i]; - - impID = req.addImpression(slotDef.width, slotDef.height, slotDef.bidfloor, slotDef.bidfloorcur, slotDef.id, slotDef.siteID); - if (impID) { - _IndexRequestData.impIDToSlotID[req.requestID][impID] = String(slotDef.id); - } - } - if (typeof cygnus_index_args.targetMode === 'number') { - _IndexRequestData.reqOptions[req.requestID].targetMode = cygnus_index_args.targetMode; - } - if (typeof cygnus_index_args.callback === 'function') { - _IndexRequestData.reqOptions[req.requestID].callback = cygnus_index_args.callback; - } - return req.buildRequest(); - } catch (e) {} +var cygnus_index_adunits = [[728, 90], [120, 600], [300, 250], [160, 600], [336, 280], [234, 60], [300, 600], [300, 50], [320, 50], [970, 250], [300, 1050], [970, 90], [180, 150]]; // jshint ignore:line + +var cygnus_index_start = function () { + window.cygnus_index_args.parseFn = cygnus_index_parse_res; + var escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; + var meta = { + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"': '\\"', + '\\': '\\\\' + }; + + function escapeCharacter(character) { + var escaped = meta[character]; + if (typeof escaped === 'string') { + return escaped; + } else { + return '\\u' + ('0000' + character.charCodeAt(0).toString(16)).slice(-4); + } + } + + function quote(string) { + escapable.lastIndex = 0; + if (escapable.test(string)) { + return string.replace(escapable, escapeCharacter); + } else { + return string; + } + } + + function OpenRTBRequest(siteID, parseFn, timeoutDelay) { + this.initialized = false; + if (typeof siteID !== 'number' || siteID % 1 !== 0 || siteID < 0) { + throw 'Invalid Site ID'; + } + + if (typeof timeoutDelay === 'number' && timeoutDelay % 1 === 0 && timeoutDelay >= 0) { + this.timeoutDelay = timeoutDelay; + } + + this.siteID = siteID; + this.impressions = []; + this._parseFnName = undefined; + if (top === self) { + this.sitePage = location.href; + this.topframe = 1; + } else { + this.sitePage = document.referrer; + this.topframe = 0; + } + + if (typeof parseFn !== 'undefined') { + if (typeof parseFn === 'function') { + this._parseFnName = 'cygnus_index_args.parseFn'; + } else { + throw 'Invalid jsonp target function'; + } + } + + if (typeof _IndexRequestData.requestCounter === 'undefined') { + _IndexRequestData.requestCounter = Math.floor(Math.random() * 256); + } else { + _IndexRequestData.requestCounter = (_IndexRequestData.requestCounter + 1) % 256; + } + + this.requestID = String((new Date().getTime() % 2592000) * 256 + _IndexRequestData.requestCounter + 256); + this.initialized = true; + } + + OpenRTBRequest.prototype.serialize = function () { + var json = '{"id":' + this.requestID + ',"site":{"page":"' + quote(this.sitePage) + '"'; + if (typeof document.referrer === 'string') { + json += ',"ref":"' + quote(document.referrer) + '"'; + } + + json += '},"imp":['; + for (var i = 0; i < this.impressions.length; i++) { + var impObj = this.impressions[i]; + var ext = []; + json += '{"id":"' + impObj.id + '", "banner":{"w":' + impObj.w + ',"h":' + impObj.h + ',"topframe":' + String(this.topframe) + '}'; + if (typeof impObj.bidfloor === 'number') { + json += ',"bidfloor":' + impObj.bidfloor; + if (typeof impObj.bidfloorcur === 'string') { + json += ',"bidfloorcur":"' + quote(impObj.bidfloorcur) + '"'; + } + } + + if (typeof impObj.slotID === 'string' && (!impObj.slotID.match(/^\s*$/))) { + ext.push('"sid":"' + quote(impObj.slotID) + '"'); + } + + if (typeof impObj.siteID === 'number') { + ext.push('"siteID":' + impObj.siteID); + } + + if (ext.length > 0) { + json += ',"ext": {' + ext.join() + '}'; + } + + if (i + 1 === this.impressions.length) { + json += '}'; + } else { + json += '},'; + } + } + + json += ']}'; + return json; + }; + + OpenRTBRequest.prototype.setPageOverride = function (sitePageOverride) { + if (typeof sitePageOverride === 'string' && (!sitePageOverride.match(/^\s*$/))) { + this.sitePage = sitePageOverride; + return true; + } else { + return false; + } + }; + + OpenRTBRequest.prototype.addImpression = function (width, height, bidFloor, bidFloorCurrency, slotID, siteID) { + var impObj = { + id: String(this.impressions.length + 1) + }; + if (typeof width !== 'number' || width <= 1) { + return null; + } + + if (typeof height !== 'number' || height <= 1) { + return null; + } + + if ((typeof slotID === 'string' || typeof slotID === 'number') && String(slotID).length <= 50) { + impObj.slotID = String(slotID); + } + + impObj.w = width; + impObj.h = height; + if (bidFloor !== undefined && typeof bidFloor !== 'number') { + return null; + } + + if (typeof bidFloor === 'number') { + if (bidFloor < 0) { + return null; + } + + impObj.bidfloor = bidFloor; + if (bidFloorCurrency !== undefined && typeof bidFloorCurrency !== 'string') { + return null; + } + + impObj.bidfloorcur = bidFloorCurrency; + } + + if (typeof siteID !== 'undefined') { + if (typeof siteID === 'number' && siteID % 1 === 0 && siteID >= 0) { + impObj.siteID = siteID; + } else { + return null; + } + } + + this.impressions.push(impObj); + return impObj.id; + }; + + OpenRTBRequest.prototype.buildRequest = function () { + if (this.impressions.length === 0 || this.initialized !== true) { + return; + } + + var jsonURI = encodeURIComponent(this.serialize()); + var scriptSrc = window.location.protocol === 'https:' ? 'https://as-sec.casalemedia.com' : 'http://as.casalemedia.com'; + scriptSrc += '/headertag?v=9&x3=1&fn=cygnus_index_parse_res&s=' + this.siteID + '&r=' + jsonURI; + if (typeof this.timeoutDelay === 'number' && this.timeoutDelay % 1 === 0 && this.timeoutDelay >= 0) { + scriptSrc += '&t=' + this.timeoutDelay; + } + + return scriptSrc; + }; + + try { + if (typeof cygnus_index_args === 'undefined' || typeof cygnus_index_args.siteID === 'undefined' || typeof cygnus_index_args.slots === 'undefined') { + return; + } + + if (typeof _IndexRequestData === 'undefined') { + _IndexRequestData = {}; + _IndexRequestData.impIDToSlotID = {}; + _IndexRequestData.reqOptions = {}; + } + + var req = new OpenRTBRequest(cygnus_index_args.siteID, cygnus_index_args.parseFn, cygnus_index_args.timeout); + if (cygnus_index_args.url && typeof cygnus_index_args.url === 'string') { + req.setPageOverride(cygnus_index_args.url); + } + + _IndexRequestData.impIDToSlotID[req.requestID] = {}; + _IndexRequestData.reqOptions[req.requestID] = {}; + var slotDef, impID; + + for (var i = 0; i < cygnus_index_args.slots.length; i++) { + slotDef = cygnus_index_args.slots[i]; + + impID = req.addImpression(slotDef.width, slotDef.height, slotDef.bidfloor, slotDef.bidfloorcur, slotDef.id, slotDef.siteID); + if (impID) { + _IndexRequestData.impIDToSlotID[req.requestID][impID] = String(slotDef.id); + } + } + + if (typeof cygnus_index_args.targetMode === 'number') { + _IndexRequestData.reqOptions[req.requestID].targetMode = cygnus_index_args.targetMode; + } + + if (typeof cygnus_index_args.callback === 'function') { + _IndexRequestData.reqOptions[req.requestID].callback = cygnus_index_args.callback; + } + + return req.buildRequest(); + } catch (e) { + } }; var IndexExchangeAdapter = function IndexExchangeAdapter() { - var slotIdMap = {}; - var requiredParams = [ - /* 0 */ - 'id', - /* 1 */ - 'siteID' - ]; - var firstAdUnitCode = ''; - - function _callBids(request) { - var bidArr = request.bids; - - if (!utils.hasValidBidRequest(bidArr[0].params, requiredParams, ADAPTER_NAME)) { - return; - } - - cygnus_index_args.slots = []; - var bidCount = 0; - - //Grab the slot level data for cygnus_index_args - for (i = 0; i < bidArr.length; i++) { - var bid = bidArr[i]; - - var width; - var height; - - outer: for (var j = 0; j < bid.sizes.length; j++) { - inner: for (var k = 0; k < cygnus_index_adunits.length; k++) { - if (bid.sizes[j][0] === cygnus_index_adunits[k][0] && - bid.sizes[j][1] === cygnus_index_adunits[k][1]) { - width = bid.sizes[j][0]; - height = bid.sizes[j][1]; - break outer; - } - } - } - - if (bid.params.timeout && typeof cygnus_index_args.timeout === 'undefined') { - cygnus_index_args.timeout = bid.params.timeout; - } - - if (bid.params.siteID && typeof cygnus_index_args.siteID === 'undefined') { - cygnus_index_args.siteID = bid.params.siteID; - } - - if (bid.params.sqps && typeof cygnus_index_args.SQPS === 'undefined') { - cygnus_index_args.slots.push({ - id:"SPQS", - width: bid.params.sqps.width, - height: bid.params.sqps.height, - siteID: bid.params.sqps.siteID || cygnus_index_args.siteID - }); - } - - if (utils.hasValidBidRequest(bid.params, requiredParams, ADAPTER_NAME)) { - firstAdUnitCode = bid.placementCode; - var slotId = bid.params[requiredParams[0]]; - slotIdMap[slotId] = bid; - - if (cygnus_index_primary_request) { - cygnus_index_args.slots.push({ - id: bid.params.id, - width: width, - height: height, - siteID: bid.params.siteID || cygnus_index_args.siteID - }); - - bidCount++; - - if (bid.params.tier2SiteID) { - cygnus_index_args.slots.push({ - id: "T1_"+bid.params.id, - width: width, - height: height, - siteID: bid.params.tier2SiteID - }); - } - if (bid.params.tier3SiteID) { - cygnus_index_args.slots.push({ - id:"T2_"+bid.params.id, - width:width, - height:height, - siteID:bid.params.tier3SiteID - }); - } - } - } - bidmanager.setExpectedBidsCount(ADAPTER_CODE, bidCount); - } - cygnus_index_primary_request = false; - - adloader.loadScript(cygnus_index_start()); - - window.cygnus_index_ready_state = function() { - try { - var indexObj = _IndexRequestData.targetIDToBid; - var lookupObj = cygnus_index_args; - - if (utils.isEmpty(indexObj)) { - var bid = bidfactory.createBid(2); - bid.bidderCode = ADAPTER_CODE; - logErrorBidResponse(); - return; - } - - utils._each(indexObj, function(adContents, cpmAndSlotId) { - utils._each(slotIdMap, function(bid, adSlotId) { - var obj = cpmAndSlotId.split('_'); - var currentId = obj[0]; - var currentCPM = obj[1]; - if (currentId === adSlotId) { - var bidObj = slotIdMap[adSlotId]; - var adUnitCode = bidObj.placementCode; - var slotObj = getSlotObj(cygnus_index_args, adSlotId); - - bid = bidfactory.createBid(1); - bid.cpm = currentCPM / 100; - bid.ad = adContents[0]; - bid.ad_id = adSlotId; - bid.bidderCode = ADAPTER_CODE; - bid.width = slotObj.width; - bid.height = slotObj.height; - bid.siteID = slotObj.siteID; - - bidmanager.addBidResponse(adUnitCode, bid); - } - }); - }); - } catch (e) { - utils.logError('Error calling index adapter', ADAPTER_NAME, e); - logErrorBidResponse(); - } - }; - } - - function getSlotObj(obj, id) { - var arr = obj.slots; - var returnObj = {}; - utils._each(arr, function(value) { - if (value.id === id) { - returnObj = value; - } - }); - return returnObj; - } - - function logErrorBidResponse() { - //no bid response - var bid = bidfactory.createBid(2); - bid.bidderCode = ADAPTER_CODE; - //log error to first add unit - bidmanager.addBidResponse(firstAdUnitCode, bid); - } - - return { - callBids: _callBids - }; - //end of Rubicon bid adaptor + var slotIdMap = {}; + var requiredParams = [ + /* 0 */ + 'id', + /* 1 */ + 'siteID' + ]; + var firstAdUnitCode = ''; + + function _callBids(request) { + var bidArr = request.bids; + + if (!utils.hasValidBidRequest(bidArr[0].params, requiredParams, ADAPTER_NAME)) { + return; + } + + cygnus_index_args.slots = []; + var bidCount = 0; + + //Grab the slot level data for cygnus_index_args + for (var i = 0; i < bidArr.length; i++) { + var bid = bidArr[i]; + + var width; + var height; + + outer: for (var j = 0; j < bid.sizes.length; j++) { + inner: for (var k = 0; k < cygnus_index_adunits.length; k++) { + if (bid.sizes[j][0] === cygnus_index_adunits[k][0] && + bid.sizes[j][1] === cygnus_index_adunits[k][1]) { + width = bid.sizes[j][0]; + height = bid.sizes[j][1]; + break outer; + } + } + } + + if (bid.params.timeout && typeof cygnus_index_args.timeout === 'undefined') { + cygnus_index_args.timeout = bid.params.timeout; + } + + if (bid.params.siteID && typeof cygnus_index_args.siteID === 'undefined') { + cygnus_index_args.siteID = bid.params.siteID; + } + + if (bid.params.sqps && typeof cygnus_index_args.SQPS === 'undefined') { + cygnus_index_args.slots.push({ + id: 'SPQS', + width: bid.params.sqps.width, + height: bid.params.sqps.height, + siteID: bid.params.sqps.siteID || cygnus_index_args.siteID + }); + } + + if (utils.hasValidBidRequest(bid.params, requiredParams, ADAPTER_NAME)) { + firstAdUnitCode = bid.placementCode; + var slotId = bid.params[requiredParams[0]]; + slotIdMap[slotId] = bid; + + if (cygnus_index_primary_request) { + cygnus_index_args.slots.push({ + id: bid.params.id, + width: width, + height: height, + siteID: bid.params.siteID || cygnus_index_args.siteID + }); + + bidCount++; + + if (bid.params.tier2SiteID) { + cygnus_index_args.slots.push({ + id: 'T1_' + bid.params.id, + width: width, + height: height, + siteID: bid.params.tier2SiteID + }); + } + + if (bid.params.tier3SiteID) { + cygnus_index_args.slots.push({ + id: 'T2_' + bid.params.id, + width: width, + height: height, + siteID: bid.params.tier3SiteID + }); + } + } + } + + bidmanager.setExpectedBidsCount(ADAPTER_CODE, bidCount); + } + + cygnus_index_primary_request = false; + + adloader.loadScript(cygnus_index_start()); + + window.cygnus_index_ready_state = function () { + try { + var indexObj = _IndexRequestData.targetIDToBid; + var lookupObj = cygnus_index_args; + + if (utils.isEmpty(indexObj)) { + var bid = bidfactory.createBid(2); + bid.bidderCode = ADAPTER_CODE; + logErrorBidResponse(); + return; + } + + utils._each(indexObj, function (adContents, cpmAndSlotId) { + utils._each(slotIdMap, function (bid, adSlotId) { + var obj = cpmAndSlotId.split('_'); + var currentId = obj[0]; + var currentCPM = obj[1]; + if (currentId === adSlotId) { + var bidObj = slotIdMap[adSlotId]; + var adUnitCode = bidObj.placementCode; + var slotObj = getSlotObj(cygnus_index_args, adSlotId); + + bid = bidfactory.createBid(1); + bid.cpm = currentCPM / 100; + bid.ad = adContents[0]; + bid.ad_id = adSlotId; + bid.bidderCode = ADAPTER_CODE; + bid.width = slotObj.width; + bid.height = slotObj.height; + bid.siteID = slotObj.siteID; + + bidmanager.addBidResponse(adUnitCode, bid); + } + }); + }); + } catch (e) { + utils.logError('Error calling index adapter', ADAPTER_NAME, e); + logErrorBidResponse(); + } + }; + } + + function getSlotObj(obj, id) { + var arr = obj.slots; + var returnObj = {}; + utils._each(arr, function (value) { + if (value.id === id) { + returnObj = value; + } + }); + + return returnObj; + } + + function logErrorBidResponse() { + //no bid response + var bid = bidfactory.createBid(2); + bid.bidderCode = ADAPTER_CODE; + + //log error to first add unit + bidmanager.addBidResponse(firstAdUnitCode, bid); + } + + return { + callBids: _callBids + }; + + //end of Rubicon bid adaptor }; module.exports = IndexExchangeAdapter; diff --git a/src/adapters/openx.js b/src/adapters/openx.js index 41afb39bc2f..8929bfe8642 100644 --- a/src/adapters/openx.js +++ b/src/adapters/openx.js @@ -1,5 +1,4 @@ -var CONSTANTS = require('../constants.json'); -var utils = require('../utils.js'); +// jshint ignore:start var bidfactory = require('../bidfactory.js'); var bidmanager = require('../bidmanager.js'); var adloader = require('../adloader'); @@ -16,100 +15,106 @@ var adloader = require('../adloader'); */ 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; - } - if (bid.params.refererURL) { - opts.refererURL = bid.params.refererURL; - } - if (bid.params.jstag_url) { - scriptUrl = bid.params.jstag_url; - } - if (bid.params.pgid) { - opts.pgid = bid.params.pgid; - } - } - _requestBids(); - } - - function _requestBids() { - - if (scriptUrl) { - adloader.loadScript(scriptUrl, function() { - var i; - var POX = OX(); - - POX.setPageURL(opts.pageURL); - POX.setRefererURL(opts.refererURL); - POX.addPage(opts.pgid); - - // Add each ad unit ID - for (i = 0; i < bids.length; i++) { - POX.addAdUnit(bids[i].params.unit); - } - - 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'); - // 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(); - }); - } - } - - return { - callBids: _callBids - }; + 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; + } + + if (bid.params.refererURL) { + opts.refererURL = bid.params.refererURL; + } + + if (bid.params.jstag_url) { + scriptUrl = bid.params.jstag_url; + } + + if (bid.params.pgid) { + opts.pgid = bid.params.pgid; + } + } + + _requestBids(); + } + + function _requestBids() { + + if (scriptUrl) { + adloader.loadScript(scriptUrl, function () { + var i; + var POX = OX(); + + POX.setPageURL(opts.pageURL); + POX.setRefererURL(opts.refererURL); + POX.addPage(opts.pgid); + + // Add each ad unit ID + for (i = 0; i < bids.length; i++) { + POX.addAdUnit(bids[i].params.unit); + } + + 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'); + + // 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(); + }); + } + } + + return { + callBids: _callBids + }; }; module.exports = OpenxAdapter; diff --git a/src/adapters/pubmatic.js b/src/adapters/pubmatic.js index e204062c8a5..5ad448b49d9 100644 --- a/src/adapters/pubmatic.js +++ b/src/adapters/pubmatic.js @@ -1,8 +1,6 @@ -var CONSTANTS = require('../constants.json'); var utils = require('../utils.js'); var bidfactory = require('../bidfactory.js'); var bidmanager = require('../bidmanager.js'); -var adloader = require('../adloader'); /** * Adapter for requesting bids from Pubmatic. @@ -12,113 +10,115 @@ var adloader = require('../adloader'); */ var PubmaticAdapter = function PubmaticAdapter() { - var bids; - var _pm_pub_id; - var _pm_optimize_adslots = []; - - function _callBids(params) { - bids = params.bids; - for (var i = 0; i < bids.length; i++) { - var bid = bids[i]; - bidmanager.pbCallbackMap['' + bid.params.adSlot] = bid; - _pm_pub_id = _pm_pub_id || bid.params.publisherId; - _pm_optimize_adslots.push(bid.params.adSlot); - } - - // Load pubmatic script in an iframe, because they call document.write - _getBids(); - } - - function _getBids() { - - // required variables for pubmatic pre-bid call - window.pm_pub_id = _pm_pub_id; - window.pm_optimize_adslots = _pm_optimize_adslots; - - //create the iframe - var iframe = utils.createInvisibleIframe(); - var elToAppend = document.getElementsByTagName('head')[0]; - //insert the iframe into document - elToAppend.insertBefore(iframe, elToAppend.firstChild); - //todo make this more browser friendly - var iframeDoc = iframe.contentWindow.document; - iframeDoc.write(_createRequestContent()); - iframeDoc.close(); - } - - function _createRequestContent() { - var content = 'inDapIF=true;'; - content += ''; - content += ''; - content += '' + - 'window.pm_pub_id = "%%PM_PUB_ID%%";' + - 'window.pm_optimize_adslots = [%%PM_OPTIMIZE_ADSLOTS%%];'; - content += ''; - - var map = {}; - map['PM_PUB_ID'] = _pm_pub_id; - map['PM_OPTIMIZE_ADSLOTS'] = _pm_optimize_adslots.map(function(adSlot) { - return "'" + adSlot + "'"; - }).join(','); - - content += ''; - content += ''; - content += 'window.parent.pbjs.handlePubmaticCallback({progKeyValueMap: progKeyValueMap, bidDetailsMap: bidDetailsMap})'; - content += ''; - content += ''; - content = utils.replaceTokenInString(content, map, '%%'); - - return content; - } - - pbjs.handlePubmaticCallback = function(response) { - var i; - var adUnit; - var adUnitInfo; - var bid; - var bidResponseMap = (response && response.bidDetailsMap) || {}; - var bidInfoMap = (response && response.progKeyValueMap) || {}; - var dimensions; - - for (i = 0; i < bids.length; i++) { - var adResponse; - bid = bids[i].params; - - adUnit = bidResponseMap[bid.adSlot] || {}; - - // adUnitInfo example: bidstatus=0;bid=0.0000;bidid=39620189@320x50;wdeal= - adUnitInfo = (bidInfoMap[bid.adSlot] || '').split(';').reduce(function(result, pair) { - var parts = pair.split('='); - result[parts[0]] = parts[1]; - return result; - }, {}); - - if (adUnitInfo.bidstatus === '1') { - dimensions = adUnitInfo.bidid.split('@')[1].split('x'); - adResponse = bidfactory.createBid(1); - adResponse.bidderCode = 'pubmatic'; - adResponse.adSlot = bid.adSlot; - adResponse.cpm = Number(adUnitInfo.bid); - adResponse.ad = unescape(adUnit.creative_tag); - adResponse.adUrl = unescape(adUnit.tracking_url); - adResponse.width = dimensions[0]; - adResponse.height = dimensions[1]; - adResponse.dealId = adUnitInfo.wdeal; - - bidmanager.addBidResponse(bids[i].placementCode, adResponse); - } else { - // Indicate an ad was not returned - adResponse = bidfactory.createBid(2); - adResponse.bidderCode = 'pubmatic'; - bidmanager.addBidResponse(bids[i].placementCode, adResponse); - } - } - }; - - return { - callBids: _callBids - }; + var bids; + var _pm_pub_id; + var _pm_optimize_adslots = []; + + function _callBids(params) { + bids = params.bids; + for (var i = 0; i < bids.length; i++) { + var bid = bids[i]; + bidmanager.pbCallbackMap['' + bid.params.adSlot] = bid; + _pm_pub_id = _pm_pub_id || bid.params.publisherId; + _pm_optimize_adslots.push(bid.params.adSlot); + } + + // Load pubmatic script in an iframe, because they call document.write + _getBids(); + } + + function _getBids() { + + // required variables for pubmatic pre-bid call + window.pm_pub_id = _pm_pub_id; + window.pm_optimize_adslots = _pm_optimize_adslots; + + //create the iframe + var iframe = utils.createInvisibleIframe(); + var elToAppend = document.getElementsByTagName('head')[0]; + + //insert the iframe into document + elToAppend.insertBefore(iframe, elToAppend.firstChild); + + //todo make this more browser friendly + var iframeDoc = iframe.contentWindow.document; + iframeDoc.write(_createRequestContent()); + iframeDoc.close(); + } + + function _createRequestContent() { + var content = 'inDapIF=true;'; + content += ''; + content += ''; + content += '' + + 'window.pm_pub_id = "%%PM_PUB_ID%%";' + + 'window.pm_optimize_adslots = [%%PM_OPTIMIZE_ADSLOTS%%];'; + content += ''; + + var map = {}; + map.PM_PUB_ID = _pm_pub_id; + map.PM_OPTIMIZE_ADSLOTS = _pm_optimize_adslots.map(function (adSlot) { + return "'" + adSlot + "'"; + }).join(','); + + content += ''; + content += ''; + content += 'window.parent.pbjs.handlePubmaticCallback({progKeyValueMap: progKeyValueMap, bidDetailsMap: bidDetailsMap})'; + content += ''; + content += ''; + content = utils.replaceTokenInString(content, map, '%%'); + + return content; + } + + pbjs.handlePubmaticCallback = function (response) { + var i; + var adUnit; + var adUnitInfo; + var bid; + var bidResponseMap = (response && response.bidDetailsMap) || {}; + var bidInfoMap = (response && response.progKeyValueMap) || {}; + var dimensions; + + for (i = 0; i < bids.length; i++) { + var adResponse; + bid = bids[i].params; + + adUnit = bidResponseMap[bid.adSlot] || {}; + + // adUnitInfo example: bidstatus=0;bid=0.0000;bidid=39620189@320x50;wdeal= + adUnitInfo = (bidInfoMap[bid.adSlot] || '').split(';').reduce(function (result, pair) { + var parts = pair.split('='); + result[parts[0]] = parts[1]; + return result; + }, {}); + + if (adUnitInfo.bidstatus === '1') { + dimensions = adUnitInfo.bidid.split('@')[1].split('x'); + adResponse = bidfactory.createBid(1); + adResponse.bidderCode = 'pubmatic'; + adResponse.adSlot = bid.adSlot; + adResponse.cpm = Number(adUnitInfo.bid); + adResponse.ad = unescape(adUnit.creative_tag); // jshint ignore:line + adResponse.adUrl = unescape(adUnit.tracking_url); // jshint ignore:line + adResponse.width = dimensions[0]; + adResponse.height = dimensions[1]; + adResponse.dealId = adUnitInfo.wdeal; + + bidmanager.addBidResponse(bids[i].placementCode, adResponse); + } else { + // Indicate an ad was not returned + adResponse = bidfactory.createBid(2); + adResponse.bidderCode = 'pubmatic'; + bidmanager.addBidResponse(bids[i].placementCode, adResponse); + } + } + }; + + return { + callBids: _callBids + }; }; -module.exports = PubmaticAdapter; \ No newline at end of file +module.exports = PubmaticAdapter; diff --git a/src/adapters/pulsepoint.js b/src/adapters/pulsepoint.js index df4b4953969..d59fb740e57 100644 --- a/src/adapters/pulsepoint.js +++ b/src/adapters/pulsepoint.js @@ -4,62 +4,62 @@ var adloader = require('../adloader.js'); var PulsePointAdapter = function PulsePointAdapter() { - var getJsStaticUrl = 'http://tag.contextweb.com/getjs.static.js'; - var bidUrl = 'http://tag.contextweb.com/bid'; - - function _callBids(params) { - if(typeof window.pp === 'undefined') { - adloader.loadScript(getJsStaticUrl, function() { bid(params); }); - } else { - bid(params); - } - } - - function bid(params) { - var bids = params.bids; - for (var i = 0; i < bids.length; i++) { - var bidRequest = bids[i]; - var callback = bidResponseCallback(bidRequest); - var ppBidRequest = new window.pp.Ad({ - cf : bidRequest.params.cf, - cp : bidRequest.params.cp, - ct : bidRequest.params.ct, - cn : 1, - ca : window.pp.requestActions.BID, - cu : bidUrl, - adUnitId: bidRequest.placementCode, - callback: callback - }); - ppBidRequest.display(); - } - } + var getJsStaticUrl = 'http://tag.contextweb.com/getjs.static.js'; + var bidUrl = 'http://tag.contextweb.com/bid'; - function bidResponseCallback(bid) { - return function(bidResponse) { - bidResponseAvailable(bid, bidResponse); - }; + function _callBids(params) { + if (typeof window.pp === 'undefined') { + adloader.loadScript(getJsStaticUrl, function () { bid(params); }); + } else { + bid(params); } + } - function bidResponseAvailable(bidRequest, bidResponse) { - if(bidResponse) { - var adSize = bidRequest.params.cf.toUpperCase().split('X'); - var bid = bidfactory.createBid(1); - bid.bidderCode = bidRequest.bidder; - bid.cpm = bidResponse.bidCpm; - bid.ad = bidResponse.html; - bid.width = adSize[0]; - bid.height = adSize[1]; - bidmanager.addBidResponse(bidRequest.placementCode, bid); - } else { - var passback = bidfactory.createBid(2); - passback.bidderCode = bidRequest.bidder; - bidmanager.addBidResponse(bidRequest.placementCode, passback); - } + function bid(params) { + var bids = params.bids; + for (var i = 0; i < bids.length; i++) { + var bidRequest = bids[i]; + var callback = bidResponseCallback(bidRequest); + var ppBidRequest = new window.pp.Ad({ + cf: bidRequest.params.cf, + cp: bidRequest.params.cp, + ct: bidRequest.params.ct, + cn: 1, + ca: window.pp.requestActions.BID, + cu: bidUrl, + adUnitId: bidRequest.placementCode, + callback: callback + }); + ppBidRequest.display(); } + } - return { - callBids: _callBids + function bidResponseCallback(bid) { + return function (bidResponse) { + bidResponseAvailable(bid, bidResponse); }; + } + + function bidResponseAvailable(bidRequest, bidResponse) { + if (bidResponse) { + var adSize = bidRequest.params.cf.toUpperCase().split('X'); + var bid = bidfactory.createBid(1); + bid.bidderCode = bidRequest.bidder; + bid.cpm = bidResponse.bidCpm; + bid.ad = bidResponse.html; + bid.width = adSize[0]; + bid.height = adSize[1]; + bidmanager.addBidResponse(bidRequest.placementCode, bid); + } else { + var passback = bidfactory.createBid(2); + passback.bidderCode = bidRequest.bidder; + bidmanager.addBidResponse(bidRequest.placementCode, passback); + } + } + + return { + callBids: _callBids + }; }; diff --git a/src/adapters/rubicon.js b/src/adapters/rubicon.js index 2c57b2d48ee..1e89bab8afa 100644 --- a/src/adapters/rubicon.js +++ b/src/adapters/rubicon.js @@ -1,6 +1,8 @@ /** * @file Rubicon (Rubicon) adapter */ + +// jshint ignore:start var utils = require('../utils'); var bidmanager = require('../bidmanager'); var bidfactory = require('../bidfactory'); @@ -11,265 +13,272 @@ var adloader = require('../adloader'); * Prebid adapter for Rubicon's header bidding client */ var RubiconAdapter = function RubiconAdapter() { - var RUBICONTAG_URL = (window.location.protocol) + '//ads.rubiconproject.com/header/'; - var RUBICON_OK_STATUS = 'ok'; - var RUBICON_BIDDER_CODE = 'rubicon'; - var RUBICON_SIZE_MAP = { - "728x90": 2, - "160x600": 9, - "300x600": 10, - "300x250": 15, - "320x50": 43, - "300x1050": 54, - "970x250": 57 - }; - var RUBICON_INITIALIZED = 0; - - // the fastlane creative code - var RUBICON_CREATIVE_START = ''; - - // pre-initialize the rubicon object - // needs to be attached to the window - window.rubicontag = window.rubicontag || {}; - window.rubicontag.cmd = window.rubicontag.cmd || []; - - // timestamp for logging - var _bidStart = null; - var bidCount = 0; - - /** - * Create an error bid - * @param {String} placement - the adunit path - * @param {Object} response - the (error) response from fastlane - * @return {Bid} a bid, for prebid - */ - function _errorBid(response, ads) { - var bidResponse = bidfactory.createBid(2); - bidResponse.bidderCode = RUBICON_BIDDER_CODE; - - // use the raw ads as the 'error' - bidResponse.error = ads; - return bidResponse; + var RUBICONTAG_URL = (window.location.protocol) + '//ads.rubiconproject.com/header/'; + var RUBICON_OK_STATUS = 'ok'; + var RUBICON_BIDDER_CODE = 'rubicon'; + var RUBICON_SIZE_MAP = { + '728x90': 2, + '160x600': 9, + '300x600': 10, + '300x250': 15, + '320x50': 43, + '300x1050': 54, + '970x250': 57 + }; + var RUBICON_INITIALIZED = 0; + + // the fastlane creative code + var RUBICON_CREATIVE_START = ''; + + // pre-initialize the rubicon object + // needs to be attached to the window + window.rubicontag = window.rubicontag || {}; + window.rubicontag.cmd = window.rubicontag.cmd || []; + + // timestamp for logging + var _bidStart = null; + var bidCount = 0; + + /** + * Create an error bid + * @param {String} placement - the adunit path + * @param {Object} response - the (error) response from fastlane + * @return {Bid} a bid, for prebid + */ + function _errorBid(response, ads) { + var bidResponse = bidfactory.createBid(2); + bidResponse.bidderCode = RUBICON_BIDDER_CODE; + + // use the raw ads as the 'error' + bidResponse.error = ads; + return bidResponse; + } + + /** + * Sort function for CPM + * @param {Object} adA + * @param {Object} adB + * @return {Float} sort order value + */ + function _adCpmSort(adA, adB) { + return (adB.cpm || 0.0) - (adA.cpm || 0.0); + } + + /** + * Produce the code to render a creative + * @param {String} elemId the element passed to rubicon; this is essentially the ad-id + * @param {Array} size array of width, height + * @return {String} creative + */ + function _creative(elemId, size) { + + // convert the size to a rubicon sizeId + var sizeId = RUBICON_SIZE_MAP[size.join('x')]; + + if (!sizeId) { + utils.logError( + 'fastlane: missing sizeId for size: ' + size.join('x') + ' could not render creative', + RUBICON_BIDDER_CODE, RUBICON_SIZE_MAP); + return ''; } - /** - * Sort function for CPM - * @param {Object} adA - * @param {Object} adB - * @return {Float} sort order value - */ - function _adCpmSort(adA, adB) { - return (adB.cpm || 0.0) - (adA.cpm || 0.0); + return RUBICON_CREATIVE_START + elemId + '", "' + sizeId + RUBICON_CREATIVE_END; + } + + /** + * Create a (successful) bid for a unit, + * based on the given response + * @param {String} placement placement code/unit path + * @param {Object} response the response from rubicon + * @return {Bid} a bid objectj + */ + function _makeBid(response, ads) { + + // if there are multiple ads, sort by CPM + ads = ads.sort(_adCpmSort); + + var bidResponse = bidfactory.createBid(1); + var ad = ads[0]; + var size = ad.dimensions; + + if (!size) { + // this really shouldn't happen + utils.logError('no dimensions given', RUBICON_BIDDER_CODE, ad); + return _errorBid(response, ads); } - /** - * Produce the code to render a creative - * @param {String} elemId the element passed to rubicon; this is essentially the ad-id - * @param {Array} size array of width, height - * @return {String} creative - */ - function _creative(elemId, size) { - - // convert the size to a rubicon sizeId - var sizeId = RUBICON_SIZE_MAP[size.join('x')]; - - if (!sizeId) { - utils.logError( - 'fastlane: missing sizeId for size: ' + size.join('x') + ' could not render creative', - RUBICON_BIDDER_CODE, RUBICON_SIZE_MAP); - return ''; - } - - return RUBICON_CREATIVE_START + elemId + '", "' + sizeId + RUBICON_CREATIVE_END; + bidResponse.bidderCode = RUBICON_BIDDER_CODE; + bidResponse.cpm = ad.cpm; + + // the element id is what the iframe will use to render + // itself using the rubicontag.renderCreative API + bidResponse.ad = _creative(response.getElementId(), size); + bidResponse.width = size[0]; + bidResponse.height = size[1]; + return bidResponse; + } + + /** + * Add a success/error bid based + * on the response from rubicon + * @param {Object} response -- AJAX response from fastlane + */ + function _addBid(response, ads) { + // get the bid for the placement code + var bid; + if (!ads || ads.length === 0) { + bid = _errorBid(response, ads); + } else { + bid = _makeBid(response, ads); } - /** - * Create a (successful) bid for a unit, - * based on the given response - * @param {String} placement placement code/unit path - * @param {Object} response the response from rubicon - * @return {Bid} a bid objectj - */ - function _makeBid(response, ads) { - - // if there are multiple ads, sort by CPM - ads = ads.sort(_adCpmSort); - - var bidResponse = bidfactory.createBid(1), - ad = ads[0], - size = ad.dimensions; - - if (!size) { - // this really shouldn't happen - utils.logError('no dimensions given', RUBICON_BIDDER_CODE, ad); - return _errorBid(response, ads); + bidmanager.addBidResponse(response.getSlotName(), bid); + } + + /** + * Helper to queue functions on rubicontag + * ready/available + * @param {Function} callback + */ + function _rready(callback) { + window.rubicontag.cmd.push(callback); + } + + /** + * download the rubicontag sdk + * @param {Object} options + * @param {String} options.accountId + * @param {Function} callback + */ + function _initSDK(options, done) { + if (RUBICON_INITIALIZED) return; + RUBICON_INITIALIZED = 1; + var accountId = options.accountId; + adloader.loadScript(RUBICONTAG_URL + accountId + '.js', done); + } + + /** + * map the sizes in `bid.sizes` to Rubicon specific keys + * @param {object} array of bids + * @return {[type]} [description] + */ + function _mapSizes(bids) { + utils._each(bids, function (bid) { + if (bid.params.sizes) { + return; + } + + //return array like ['300x250', '728x90'] + var parsedSizes = utils.parseSizesInput(bid.sizes); + + //iterate the bid.sizes array to lookup codes + var tempSize = []; + for (var i = 0; i < parsedSizes.length; i++) { + var rubiconKey = RUBICON_SIZE_MAP[parsedSizes[i]]; + if (rubiconKey) { + tempSize.push(rubiconKey); } - - bidResponse.bidderCode = RUBICON_BIDDER_CODE; - bidResponse.cpm = ad.cpm; - // the element id is what the iframe will use to render - // itself using the rubicontag.renderCreative API - bidResponse.ad = _creative(response.getElementId(), size); - bidResponse.width = size[0]; - bidResponse.height = size[1]; - return bidResponse; - } - - /** - * Add a success/error bid based - * on the response from rubicon - * @param {Object} response -- AJAX response from fastlane - */ - function _addBid(response, ads) { - // get the bid for the placement code - var bid; - if (!ads || ads.length === 0) { - bid = _errorBid(response, ads); - } else { - bid = _makeBid(response, ads); + } + + bid.params.sizes = tempSize; + }); + } + + /** + * Define the slot using the rubicontag.defineSlot API + * @param {Object} Bidrequest + */ + function _defineSlot(bid) { + _rready(function () { + var newSlot = window.rubicontag.defineSlot({ + siteId: bid.params.siteId, + zoneId: bid.params.zoneId, + id: bid.placementCode, + sizes: bid.params.sizes + }); + if (bid.params.position) { + newSlot.setPosition(bid.params.position); + } + + if (bid.params.userId) { + window.rubicontag.setUserKey(bid.params.userId); + } + + if (bid.params.keywords) { + for (var i = 0; i < bid.params.keywords.length; i++) { + newSlot.addKW(bid.params.keywords[i]); } + } - bidmanager.addBidResponse(response.getSlotName(), bid); - } - - /** - * Helper to queue functions on rubicontag - * ready/available - * @param {Function} callback - */ - function _rready(callback) { - window.rubicontag.cmd.push(callback); - } - - /** - * download the rubicontag sdk - * @param {Object} options - * @param {String} options.accountId - * @param {Function} callback - */ - function _initSDK(options, done) { - if (RUBICON_INITIALIZED) return; - RUBICON_INITIALIZED = 1; - var accountId = options.accountId; - adloader.loadScript(RUBICONTAG_URL + accountId + '.js', done); - } - - /** - * map the sizes in `bid.sizes` to Rubicon specific keys - * @param {object} array of bids - * @return {[type]} [description] - */ - function _mapSizes(bids){ - utils._each(bids, function(bid){ - if(bid.params.sizes){ - return; - } - //return array like ['300x250', '728x90'] - var parsedSizes = utils.parseSizesInput(bid.sizes); - //iterate the bid.sizes array to lookup codes - var tempSize = []; - for(var i =0; i < parsedSizes.length; i++){ - var rubiconKey = RUBICON_SIZE_MAP[parsedSizes[i]]; - if(rubiconKey){ - tempSize.push(rubiconKey); - } - } - bid.params.sizes = tempSize; - }); - } - - /** - * Define the slot using the rubicontag.defineSlot API - * @param {Object} Bidrequest - */ - function _defineSlot(bid) { - _rready(function () { - var newSlot=window.rubicontag.defineSlot({ - siteId: bid.params.siteId, - zoneId: bid.params.zoneId, - id: bid.placementCode, - sizes: bid.params.sizes - }); - if (bid.params.position) { - newSlot.setPosition(bid.params.position); - } - if (bid.params.userId) { - window.rubicontag.setUserKey(bid.params.userId); - } - if (bid.params.keywords) { - for(var i=0; i < bid.params.keywords.length; i++){ - newSlot.addKW(bid.params.keywords[i]); - } - } - if (bid.params.inventory) { - for (var p in bid.params.inventory) { - if (bid.params.inventory.hasOwnProperty(p)) { - newSlot.addFPI(p,bid.params.inventory[p]); - } - } - } - if (bid.params.visitor) { - for (var p in bid.params.visitor) { - if (bid.params.visitor.hasOwnProperty(p)) { - newSlot.addFPV(p,bid.params.visitor[p]); - } - } - } - }); - } - - /** - * Handle the bids received (from rubicon) - */ - function _bidsReady() { - // NOTE: we don't really need to do anything, - // because right now we're shimming XMLHttpRequest.open, - // but in the future we'll get data from rubicontag here - utils.logMessage('Rubicon Project bidding complete: ' + ((new Date).getTime() - _bidStart)); - - utils._each(rubicontag.getAllSlots(), function (slot) { - _addBid(slot, slot.getRawResponses()); - }); - } - + if (bid.params.inventory) { + for (var p in bid.params.inventory) { + if (bid.params.inventory.hasOwnProperty(p)) { + newSlot.addFPI(p, bid.params.inventory[p]); + } + } + } + if (bid.params.visitor) { + for (var p in bid.params.visitor) { + if (bid.params.visitor.hasOwnProperty(p)) { + newSlot.addFPV(p, bid.params.visitor[p]); + } + } + } + }); + } + + /** + * Handle the bids received (from rubicon) + */ + function _bidsReady() { + // NOTE: we don't really need to do anything, + // because right now we're shimming XMLHttpRequest.open, + // but in the future we'll get data from rubicontag here + utils.logMessage('Rubicon Project bidding complete: ' + ((new Date).getTime() - _bidStart)); + + utils._each(rubicontag.getAllSlots(), function (slot) { + _addBid(slot, slot.getRawResponses()); + }); + } + + /** + * Request the specified bids from + * Rubicon + * @param {Object} params the bidder-level params (from prebid) + * @param {Array} params.bids the bids requested + */ + function _callBids(params) { + + // start the timer; want to measure from + // even just loading the SDK + _bidStart = (new Date).getTime(); + + _mapSizes(params.bids); + + utils._each(params.bids, function (bid, index) { + // on the first bid, set up the SDK + // the config will be set on each bid + if (index === 0) { + _initSDK(bid.params); + } + + _defineSlot(bid); + }); + + _rready(function () { + window.rubicontag.run(_bidsReady); + }); + } + + return { /** - * Request the specified bids from - * Rubicon - * @param {Object} params the bidder-level params (from prebid) - * @param {Array} params.bids the bids requested + * @public callBids + * the interface to Prebid */ - function _callBids(params) { - - // start the timer; want to measure from - // even just loading the SDK - _bidStart = (new Date).getTime(); - - _mapSizes(params.bids); - - utils._each(params.bids, function (bid, index) { - // on the first bid, set up the SDK - // the config will be set on each bid - if (index === 0) { - _initSDK(bid.params); - } - - _defineSlot(bid); - }); - - _rready(function () { - window.rubicontag.run(_bidsReady); - }); - } - - return { - /** - * @public callBids - * the interface to Prebid - */ - callBids: _callBids - }; + callBids: _callBids + }; }; module.exports = RubiconAdapter; diff --git a/src/adapters/rubiconLegacy.js b/src/adapters/rubiconLegacy.js index d6fd04c0f6c..5c448ca7cd7 100644 --- a/src/adapters/rubiconLegacy.js +++ b/src/adapters/rubiconLegacy.js @@ -1,4 +1,6 @@ //Factory for creating the bidderAdaptor +// jshint ignore:start + var CONSTANTS = require('../constants.json'); var utils = require('../utils.js'); var bidfactory = require('../bidfactory.js'); @@ -8,184 +10,186 @@ var bidmanager = require('../bidmanager.js'); * This adapter is deprecated and should not be used. Please use rubicon.js instead */ var RubiconAdapter = function RubiconAdapter() { - // Map size dimensions to size 'ID' - var sizeMap = {}; - - function callBids(params) { - var bidArr = params.bids; - for (var i = 0; i < bidArr.length; i++) { - var bid = bidArr[i]; - //get the first size in the array - //TODO validation - var width = bid.sizes[0][0]; - var height = bid.sizes[0][1]; - var iframeContents = createRequestContent(bid, 'window.parent.pbjs.handleRubiconCallback', width, height); - var iframeId = loadIframeContent(iframeContents); - bid.iframeId = iframeId; - bidmanager.pbCallbackMap[getBidId(bid)] = bid; - } - - } - - // Build an ID that can be used to identify the response to the bid request. There - // may be an identifier we can send that gets sent back to us. - function getBidId(bid) { - return (bid.params ? [bid.params.rp_account, bid.params.rp_site, bid.params.rp_zonesize] : - [bid.account_id, bid.site_id, bid.zone_id, bid.size_id]).join('-'); - - } - - function loadIframeContent(content, callback) { - //create the iframe - var iframe = utils.createInvisibleIframe(); - var elToAppend = document.getElementsByTagName('head')[0]; - //insert the iframe into document - elToAppend.insertBefore(iframe, elToAppend.firstChild); - //todo make this more browser friendly - var iframeDoc = iframe.contentWindow.document; - iframeDoc.write(content); - iframeDoc.close(); - - return iframe.id; - - } - - function createRequestContent(bidOptions, callback, width, height) { - - // Map the size 'ID' to the dimensions - sizeMap[bidOptions.params.rp_zonesize.split('-')[1]] = { - width: width, - height: height - }; - - var content = 'inDapIF=true;'; - content += ''; - content += ''; - - - content += '' + - 'window.rp_account = "%%RP_ACCOUNT%%";' + - 'window.rp_site = "%%RP_SITE%%";' + - 'window.rp_zonesize = "%%RP_ZONESIZE%%";' + - 'window.rp_tracking = "%%RP_TRACKING%%";' + - 'window.rp_visitor = %%RP_VISITOR%%;' + - 'window.rp_width = %%RP_WIDTH%%;' + - 'window.rp_height = %%RP_HEIGHT%%;' + - 'window.rp_adtype = "jsonp";' + - 'window.rp_inventory = %%RP_INVENTORY%% ;' + - 'window.rp_floor=%%RP_FLOOR%%;' + - 'window.rp_fastlane = true;' + - 'window.rp_callback = ' + callback + ';'; - - - var map = {}; - map['RP_ACCOUNT'] = bidOptions.params.rp_account; - map['RP_SITE'] = bidOptions.params.rp_site; - map['RP_ZONESIZE'] = bidOptions.params.rp_zonesize; - map['RP_TRACKING'] = (bidOptions.params.rp_tracking) ? bidOptions.params.rp_tracking : ''; - map['RP_VISITOR'] = bidOptions.params.rp_visitor ? bidOptions.params.rp_visitor : '{}'; - map['RP_WIDTH'] = width; - map['RP_HEIGHT'] = height; - map['RP_INVENTORY'] = bidOptions.params.rp_inventory || '{}'; - map['RP_FLOOR'] = bidOptions.params.rp_floor ? bidOptions.params.rp_floor : '0.00'; - - content += ''; - content += ''; - content += ''; - - content = utils.replaceTokenInString(content, map, '%%'); - - //console.log(content); - - return content; - - } - - window.pbjs = window.pbjs || {que: []}; - window.pbjs.handleRubiconCallback = function(response) { - var placementCode = ''; - - var bid = {}; - if (response && response.status === 'ok') { - try { - var iframeId = ''; - var bidObj = bidmanager.getPlacementIdByCBIdentifer(getBidId(response)); - if (bidObj) { - placementCode = bidObj.placementCode; - bidObj.status = CONSTANTS.STATUS.GOOD; - iframeId = bidObj.iframeId; - } - - if (response.ads && response.ads[0] && response.ads[0].status === 'ok') { - bid = bidfactory.createBid(1); - - var rubiconAd = response.ads[0]; - var size = sizeMap[rubiconAd.size_id]; - var width = 0; - var height = 0; - - var iframeObj = window.frames[iframeId]; - var rubiconObj; - if(iframeObj.contentWindow){ - rubiconObj = iframeObj.contentWindow.RubiconAdServing - }else{ - rubiconObj = iframeObj.window.RubiconAdServing; - } - - if (rubiconObj && rubiconObj.AdSizes) { - /* should return - 1: { - dim: "468x60" - }, - */ - size = rubiconObj.AdSizes[rubiconAd.size_id]; - var sizeArray = size.dim.split('x'); - width = sizeArray[0]; - height = sizeArray[1]; - } - - bid.cpm = rubiconAd.cpm; - bid.ad = ''; - bid.ad_id = rubiconAd.ad_id; - bid.bidderCode = 'rubicon'; - bid.sizeId = rubiconAd.size_id; - bid.width = width; - bid.height = height; - - }else{ - bid = bidfactory.createBid(2); - bid.bidderCode = 'rubicon'; - var bidObj = bidmanager.getPlacementIdByCBIdentifer(getBidId(response)); - if (bidObj) { - placementCode = bidObj.placementCode; - } - } - - } catch (e) { - utils.logError('Error parsing rubicon response bid: ' + e.message); - } - - } else { - //set bid response code to 2 = no response or error - bid = bidfactory.createBid(2); - bid.bidderCode = 'rubicon'; - var bidObj = bidmanager.getPlacementIdByCBIdentifer(getBidId(response)); - if (bidObj) { - placementCode = bidObj.placementCode; - } - - } - - //add the bid response here - bidmanager.addBidResponse(placementCode, bid); - - }; - - return { - callBids: callBids - - }; - //end of Rubicon bid adaptor + // Map size dimensions to size 'ID' + var sizeMap = {}; + + function callBids(params) { + var bidArr = params.bids; + for (var i = 0; i < bidArr.length; i++) { + var bid = bidArr[i]; + + //get the first size in the array + //TODO validation + var width = bid.sizes[0][0]; + var height = bid.sizes[0][1]; + var iframeContents = createRequestContent(bid, 'window.parent.pbjs.handleRubiconCallback', width, height); + var iframeId = loadIframeContent(iframeContents); + bid.iframeId = iframeId; + bidmanager.pbCallbackMap[getBidId(bid)] = bid; + } + + } + + // Build an ID that can be used to identify the response to the bid request. There + // may be an identifier we can send that gets sent back to us. + function getBidId(bid) { + return (bid.params ? [bid.params.rp_account, bid.params.rp_site, bid.params.rp_zonesize] : + [bid.account_id, bid.site_id, bid.zone_id, bid.size_id]).join('-'); + + } + + function loadIframeContent(content, callback) { + //create the iframe + var iframe = utils.createInvisibleIframe(); + var elToAppend = document.getElementsByTagName('head')[0]; + + //insert the iframe into document + elToAppend.insertBefore(iframe, elToAppend.firstChild); + + //todo make this more browser friendly + var iframeDoc = iframe.contentWindow.document; + iframeDoc.write(content); + iframeDoc.close(); + + return iframe.id; + + } + + function createRequestContent(bidOptions, callback, width, height) { + + // Map the size 'ID' to the dimensions + sizeMap[bidOptions.params.rp_zonesize.split('-')[1]] = { + width: width, + height: height + }; + + var content = 'inDapIF=true;'; + content += ''; + content += ''; + + content += '' + + 'window.rp_account = "%%RP_ACCOUNT%%";' + + 'window.rp_site = "%%RP_SITE%%";' + + 'window.rp_zonesize = "%%RP_ZONESIZE%%";' + + 'window.rp_tracking = "%%RP_TRACKING%%";' + + 'window.rp_visitor = %%RP_VISITOR%%;' + + 'window.rp_width = %%RP_WIDTH%%;' + + 'window.rp_height = %%RP_HEIGHT%%;' + + 'window.rp_adtype = "jsonp";' + + 'window.rp_inventory = %%RP_INVENTORY%% ;' + + 'window.rp_floor=%%RP_FLOOR%%;' + + 'window.rp_fastlane = true;' + + 'window.rp_callback = ' + callback + ';'; + + var map = {}; + map.RP_ACCOUNT = bidOptions.params.rp_account; + map.RP_SITE = bidOptions.params.rp_site; + map.RP_ZONESIZE = bidOptions.params.rp_zonesize; + map.RP_TRACKING = (bidOptions.params.rp_tracking) ? bidOptions.params.rp_tracking : ''; + map.RP_VISITOR = bidOptions.params.rp_visitor ? bidOptions.params.rp_visitor : '{}'; + map.RP_WIDTH = width; + map.RP_HEIGHT = height; + map.RP_INVENTORY = bidOptions.params.rp_inventory || '{}'; + map.RP_FLOOR = bidOptions.params.rp_floor ? bidOptions.params.rp_floor : '0.00'; + + content += ''; + content += ''; + content += ''; + + content = utils.replaceTokenInString(content, map, '%%'); + + //console.log(content); + + return content; + + } + + window.pbjs = window.pbjs || { que: [] }; + window.pbjs.handleRubiconCallback = function (response) { + var placementCode = ''; + + var bid = {}; + if (response && response.status === 'ok') { + try { + var iframeId = ''; + var bidObj = bidmanager.getPlacementIdByCBIdentifer(getBidId(response)); + if (bidObj) { + placementCode = bidObj.placementCode; + bidObj.status = CONSTANTS.STATUS.GOOD; + iframeId = bidObj.iframeId; + } + + if (response.ads && response.ads[0] && response.ads[0].status === 'ok') { + bid = bidfactory.createBid(1); + + var rubiconAd = response.ads[0]; + var size = sizeMap[rubiconAd.size_id]; + var width = 0; + var height = 0; + + var iframeObj = window.frames[iframeId]; + var rubiconObj; + if (iframeObj.contentWindow) { + rubiconObj = iframeObj.contentWindow.RubiconAdServing; + } else { + rubiconObj = iframeObj.window.RubiconAdServing; + } + + if (rubiconObj && rubiconObj.AdSizes) { + /* should return + 1: { + dim: "468x60" + }, + */ + size = rubiconObj.AdSizes[rubiconAd.size_id]; + var sizeArray = size.dim.split('x'); + width = sizeArray[0]; + height = sizeArray[1]; + } + + bid.cpm = rubiconAd.cpm; + bid.ad = ''; + bid.ad_id = rubiconAd.ad_id; + bid.bidderCode = 'rubicon'; + bid.sizeId = rubiconAd.size_id; + bid.width = width; + bid.height = height; + + } else { + bid = bidfactory.createBid(2); + bid.bidderCode = 'rubicon'; + var bidObj = bidmanager.getPlacementIdByCBIdentifer(getBidId(response)); + if (bidObj) { + placementCode = bidObj.placementCode; + } + } + + } catch (e) { + utils.logError('Error parsing rubicon response bid: ' + e.message); + } + + } else { + //set bid response code to 2 = no response or error + bid = bidfactory.createBid(2); + bid.bidderCode = 'rubicon'; + var bidObj = bidmanager.getPlacementIdByCBIdentifer(getBidId(response)); + if (bidObj) { + placementCode = bidObj.placementCode; + } + + } + + //add the bid response here + bidmanager.addBidResponse(placementCode, bid); + + }; + + return { + callBids: callBids + + }; + + //end of Rubicon bid adaptor }; -module.exports = RubiconAdapter; \ No newline at end of file +module.exports = RubiconAdapter; diff --git a/src/adapters/sovrn.js b/src/adapters/sovrn.js index 682df269d92..2170db52602 100644 --- a/src/adapters/sovrn.js +++ b/src/adapters/sovrn.js @@ -10,187 +10,188 @@ var allPlacementCodes; * Adapter for requesting bids from Sovrn */ var SovrnAdapter = function SovrnAdapter() { - var sovrnUrl = 'ap.lijit.com/rtb/bid'; - - function _callBids(params) { - var sovrnBids = params.bids || []; - // De-dupe by tagid then issue single bid request for all bids - _requestBids(_getUniqueTagids(sovrnBids)); - } - - // filter bids to de-dupe them? - function _getUniqueTagids(bids) { - var key; - var map = {}; - var Tagids = []; - bids.forEach(function(bid) { - map[utils.getBidIdParamater('tagid', bid.params)] = bid; - }); - for (key in map) { - if (map.hasOwnProperty(key)) { - Tagids.push(map[key]); - } - } - return Tagids; - } - - function _requestBids(bidReqs) { - // build bid request object - var domain = window.location.host; - var page = window.location.pathname + location.search + location.hash; - - var sovrnImps = []; - allPlacementCodes = []; - //build impression array for sovrn - utils._each(bidReqs, function(bid) - { - var tagId = utils.getBidIdParamater('tagid', bid.params); - var bidFloor = utils.getBidIdParamater('bidfloor', bid.params); - var adW=0,adH=0; - - //sovrn supports only one size per tagid, so we just take the first size if there are more - //if we are a 2 item array of 2 numbers, we must be a SingleSize array - var sizeArrayLength = bid.sizes.length; - if (sizeArrayLength === 2 && typeof bid.sizes[0] === 'number' && typeof bid.sizes[1] === 'number') { - adW=bid.sizes[0]; - adH=bid.sizes[1]; - } - else - { - adW=bid.sizes[0][0]; - adH=bid.sizes[0][1]; - } - var imp = - { - id: utils.getUniqueIdentifierStr(), - banner: { - w: adW, - h: adH - }, - tagid: tagId, - bidfloor: bidFloor - }; - sovrnImps.push(imp); - bidmanager.pbCallbackMap[imp.id] = bid; - allPlacementCodes.push(bid.placementCode); - }); - - // build bid request with impressions - var sovrnBidReq = { - id: utils.getUniqueIdentifierStr(), - imp: sovrnImps, - site:{ - domain: domain, - page: page - } - }; - - var scriptUrl = '//'+sovrnUrl+'?callback=window.pbjs.sovrnResponse' + - '&br=' + encodeURIComponent(JSON.stringify(sovrnBidReq)); - adloader.loadScript(scriptUrl, null); - } - - function addBlankBidResponsesForAllPlacementsExceptThese(placementsWithBidsBack){ - utils._each(allPlacementCodes, function(placementCode) - { - if(utils.contains(placementsWithBidsBack, placementCode)) { - // A bid was returned for this placement already - } else { - // Add a no-bid response for this placement. - var bid = {}; - bid = bidfactory.createBid(2); - bid.bidderCode = 'sovrn'; - bidmanager.addBidResponse(placementCode, bid); - } - }); - } - - - //expose the callback to the global object: - pbjs.sovrnResponse = function(sovrnResponseObj) { - // valid object? - if (sovrnResponseObj && sovrnResponseObj.id) { - // valid object w/ bid responses? - if (sovrnResponseObj.seatbid && sovrnResponseObj.seatbid.length !==0 && sovrnResponseObj.seatbid[0].bid && sovrnResponseObj.seatbid[0].bid.length !== 0) { - var placementsWithBidsBack = []; - sovrnResponseObj.seatbid[0].bid.forEach(function(sovrnBid){ - - var responseCPM; - var placementCode = ''; - var id = sovrnBid.impid; - var bid = {}; - - // try to fetch the bid request we sent Sovrn - var bidObj = bidmanager.getPlacementIdByCBIdentifer(id); - if (bidObj){ - placementCode = bidObj.placementCode; - placementsWithBidsBack.push(placementCode); - bidObj.status = CONSTANTS.STATUS.GOOD; - - //place ad response on bidmanager._adResponsesByBidderId - responseCPM = parseFloat(sovrnBid.price); - - if(responseCPM !== 0) { - sovrnBid.placementCode = placementCode; - sovrnBid.size = bidObj.sizes; - var responseAd = sovrnBid.adm; - - // build impression url from response - var responseNurl = ''; - - //store bid response - //bid status is good (indicating 1) - bid = bidfactory.createBid(1); - bid.creative_id = sovrnBid.Id; - bid.bidderCode = 'sovrn'; - bid.cpm = responseCPM; - - //set ad content + impression url - // sovrn returns ' + - ''; - }, - /** - * Bid response builder. - * - * @param {Object} slotCriteria - Yieldbot bid criteria - * @private - */ - buildBid: function(slotCriteria) { - var bid = {}; - - if (slotCriteria && slotCriteria.ybot_ad && slotCriteria.ybot_ad !== 'n') { - - bid = bidfactory.createBid(ybotlib.BID_STATUS.AVAILABLE); - - bid.cpm = parseInt(slotCriteria.ybot_cpm) / 100.0 || 0; // Yieldbot CPM bids are in cents - - var szArr = slotCriteria.ybot_size ? slotCriteria.ybot_size.split('x') : [0,0], - slot = slotCriteria.ybot_slot || '', - sizeStr = slotCriteria.ybot_size || ''; // Creative template needs the dimensions string - - bid.width = szArr[0] || 0; - bid.height = szArr[1] || 0; - - bid.ad = ybotlib.buildCreative(slot, sizeStr); - - // Add Yieldbot parameters to allow publisher bidderSettings.yieldbot specific targeting - for (var k in slotCriteria) { - bid[k] = slotCriteria[k]; - } - - } else { - bid = bidfactory.createBid(ybotlib.BID_STATUS.EMPTY); - } - - bid.bidderCode = 'yieldbot'; - return bid; - }, - /** - * Yieldbot implementation of {@link module:adaptermanger.callBids} - * @param {Object} params - Adapter bid configuration object - * @private - */ - callBids: function(params) { - - var bids = params.bids || [], - ybotq = window.ybotq || []; - - ybotlib.pageLevelOption = false; - - ybotq.push(function () { - var yieldbot = window.yieldbot; - - utils._each(bids, function(v) { - var bid = v, - psn = bid.params && bid.params.psn || 'ERROR_DEFINE_YB_PSN', - slot = bid.params && bid.params.slot || 'ERROR_DEFINE_YB_SLOT'; - - yieldbot.pub(psn); - yieldbot.defineSlot(slot, {sizes: bid.sizes || []}); - - var cbId = utils.getUniqueIdentifierStr(); - bidmanager.pbCallbackMap[cbId] = bid; - ybotlib.definedSlots.push(cbId); - }); - - yieldbot.enableAsync(); - yieldbot.go(); - }); - - ybotq.push(function () { - ybotlib.handleUpdateState(); - }); - - adloader.loadScript('//cdn.yldbt.com/js/yieldbot.intent.js'); - }, - /** - * Yieldbot bid request callback handler. - * - * @see {@link YieldbotAdapter~_callBids} - * @private - */ - handleUpdateState: function() { - var yieldbot = window.yieldbot; - - utils._each(ybotlib.definedSlots, function(v) { - var slot, - criteria, - placementCode, - adapterConfig; - - adapterConfig = bidmanager.getPlacementIdByCBIdentifer(v) || {}; - slot = adapterConfig.params.slot || ''; - criteria = yieldbot.getSlotCriteria(slot); - - placementCode = adapterConfig.placementCode || 'ERROR_YB_NO_PLACEMENT'; - var bid = ybotlib.buildBid(criteria); - - bidmanager.addBidResponse(placementCode, bid); - - }); + window.ybotq = window.ybotq || []; + + var ybotlib = { + BID_STATUS: { + PENDING: 0, + AVAILABLE: 1, + EMPTY: 2 + }, + definedSlots: [], + pageLevelOption: false, + /** + * Builds the Yieldbot creative tag. + * + * @param {String} slot - The slot name to bid for + * @param {String} size - The dimenstions of the slot + * @private + */ + buildCreative: function (slot, size) { + return '' + + ''; + }, + /** + * Bid response builder. + * + * @param {Object} slotCriteria - Yieldbot bid criteria + * @private + */ + buildBid: function (slotCriteria) { + var bid = {}; + + if (slotCriteria && slotCriteria.ybot_ad && slotCriteria.ybot_ad !== 'n') { + + bid = bidfactory.createBid(ybotlib.BID_STATUS.AVAILABLE); + + bid.cpm = parseInt(slotCriteria.ybot_cpm) / 100.0 || 0; // Yieldbot CPM bids are in cents + + var szArr = slotCriteria.ybot_size ? slotCriteria.ybot_size.split('x') : [0, 0]; + var slot = slotCriteria.ybot_slot || ''; + var sizeStr = slotCriteria.ybot_size || ''; // Creative template needs the dimensions string + + bid.width = szArr[0] || 0; + bid.height = szArr[1] || 0; + + bid.ad = ybotlib.buildCreative(slot, sizeStr); + + // Add Yieldbot parameters to allow publisher bidderSettings.yieldbot specific targeting + for (var k in slotCriteria) { + bid[k] = slotCriteria[k]; } + + } else { + bid = bidfactory.createBid(ybotlib.BID_STATUS.EMPTY); + } + + bid.bidderCode = 'yieldbot'; + return bid; + }, + /** + * Yieldbot implementation of {@link module:adaptermanger.callBids} + * @param {Object} params - Adapter bid configuration object + * @private + */ + callBids: function (params) { + + var bids = params.bids || []; + var ybotq = window.ybotq || []; + + ybotlib.pageLevelOption = false; + + ybotq.push(function () { + var yieldbot = window.yieldbot; + + utils._each(bids, function (v) { + var bid = v; + var psn = bid.params && bid.params.psn || 'ERROR_DEFINE_YB_PSN'; + var slot = bid.params && bid.params.slot || 'ERROR_DEFINE_YB_SLOT'; + + yieldbot.pub(psn); + yieldbot.defineSlot(slot, { sizes: bid.sizes || [] }); + + var cbId = utils.getUniqueIdentifierStr(); + bidmanager.pbCallbackMap[cbId] = bid; + ybotlib.definedSlots.push(cbId); + }); + + yieldbot.enableAsync(); + yieldbot.go(); + }); + + ybotq.push(function () { + ybotlib.handleUpdateState(); + }); + + adloader.loadScript('//cdn.yldbt.com/js/yieldbot.intent.js'); + }, + /** + * Yieldbot bid request callback handler. + * + * @see {@link YieldbotAdapter~_callBids} + * @private + */ + handleUpdateState: function () { + var yieldbot = window.yieldbot; + + utils._each(ybotlib.definedSlots, function (v) { + var slot; + var criteria; + var placementCode; + var adapterConfig; + + adapterConfig = bidmanager.getPlacementIdByCBIdentifer(v) || {}; + slot = adapterConfig.params.slot || ''; + criteria = yieldbot.getSlotCriteria(slot); + + placementCode = adapterConfig.placementCode || 'ERROR_YB_NO_PLACEMENT'; + var bid = ybotlib.buildBid(criteria); + + bidmanager.addBidResponse(placementCode, bid); + + }); } - return { - callBids: ybotlib.callBids - }; + }; + return { + callBids: ybotlib.callBids + }; }; module.exports = YieldbotAdapter; diff --git a/src/adloader.js b/src/adloader.js index 7531a05232a..f3153cb700e 100644 --- a/src/adloader.js +++ b/src/adloader.js @@ -1,55 +1,60 @@ var utils = require('./utils'); + //add a script tag to the page, used to add /jpt call to page -exports.loadScript = function(tagSrc, callback) { - if(!tagSrc) { - utils.logError('Error attempting to request empty URL', 'adloader.js:loadScript'); - return; - } - var jptScript = document.createElement('script'); - jptScript.type = 'text/javascript'; - jptScript.async = true; - - - // Execute a callback if necessary - if (callback && typeof callback === "function") { - if (jptScript.readyState) { - jptScript.onreadystatechange = function() { - if (jptScript.readyState == "loaded" || jptScript.readyState == "complete") { - jptScript.onreadystatechange = null; - callback(); - } - }; - } else { - jptScript.onload = function() { - callback(); - }; - } - } - - //call function to build the JPT call - jptScript.src = tagSrc; - - //add the new script tag to the page - var elToAppend = document.getElementsByTagName('head'); - elToAppend = elToAppend.length ? elToAppend : document.getElementsByTagName('body'); - if (elToAppend.length) { - elToAppend = elToAppend[0]; - elToAppend.insertBefore(jptScript, elToAppend.firstChild); - } +exports.loadScript = function (tagSrc, callback) { + if (!tagSrc) { + utils.logError('Error attempting to request empty URL', 'adloader.js:loadScript'); + return; + } + + var jptScript = document.createElement('script'); + jptScript.type = 'text/javascript'; + jptScript.async = true; + + // Execute a callback if necessary + if (callback && typeof callback === 'function') { + if (jptScript.readyState) { + jptScript.onreadystatechange = function () { + if (jptScript.readyState === 'loaded' || jptScript.readyState === 'complete') { + jptScript.onreadystatechange = null; + callback(); + } + }; + } else { + jptScript.onload = function () { + callback(); + }; + } + } + + //call function to build the JPT call + jptScript.src = tagSrc; + + //add the new script tag to the page + var elToAppend = document.getElementsByTagName('head'); + elToAppend = elToAppend.length ? elToAppend : document.getElementsByTagName('body'); + if (elToAppend.length) { + elToAppend = elToAppend[0]; + elToAppend.insertBefore(jptScript, elToAppend.firstChild); + } }; //track a impbus tracking pixel //TODO: Decide if tracking via AJAX is sufficent, or do we need to //run impression trackers via page pixels? -exports.trackPixel = function(pixelUrl) { - var delimiter, trackingPixel; - if (!pixelUrl || typeof(pixelUrl) !== 'string') { - utils.logMessage('Missing or invalid pixelUrl.'); - return; - } - delimiter = pixelUrl.indexOf('?') > 0 ? '&' : '?'; - //add a cachebuster so we don't end up dropping any impressions - trackingPixel = pixelUrl + delimiter + 'rnd=' + Math.floor(Math.random() * 1E7); - (new Image()).src = trackingPixel; - return trackingPixel; +exports.trackPixel = function (pixelUrl) { + let delimiter; + let trackingPixel; + + if (!pixelUrl || typeof (pixelUrl) !== 'string') { + utils.logMessage('Missing or invalid pixelUrl.'); + return; + } + + delimiter = pixelUrl.indexOf('?') > 0 ? '&' : '?'; + + //add a cachebuster so we don't end up dropping any impressions + trackingPixel = pixelUrl + delimiter + 'rnd=' + Math.floor(Math.random() * 1E7); + (new Image()).src = trackingPixel; + return trackingPixel; }; diff --git a/src/bidfactory.js b/src/bidfactory.js index 364c38d28e8..639cbc966b6 100644 --- a/src/bidfactory.js +++ b/src/bidfactory.js @@ -1,59 +1,54 @@ var utils = require('./utils.js'); /** - Required paramaters - bidderCode, - height, - width, - statusCode - Optional paramaters - adId, - cpm, - ad, - adUrl, - dealId, - priceKeyString; + Required paramaters + bidderCode, + height, + width, + statusCode + Optional paramaters + adId, + cpm, + ad, + adUrl, + dealId, + priceKeyString; */ function Bid(statusCode) { - var _bidId = utils.getUniqueIdentifierStr(), - _statusCode = statusCode || 0; - this.bidderCode = ''; - this.width = 0; - this.height = 0; - this.statusMessage = _getStatus(); - this.adId = _bidId; + var _bidId = utils.getUniqueIdentifierStr(); + var _statusCode = statusCode || 0; - function _getStatus() { - switch (_statusCode) { - case 0: - return 'Pending'; - case 1: - return 'Bid available'; - case 2: - return 'Bid returned empty or error response'; - case 3: - return 'Bid timed out'; - } - } - this.getStatusCode = function() { - return _statusCode; - }; + this.bidderCode = ''; + this.width = 0; + this.height = 0; + this.statusMessage = _getStatus(); + this.adId = _bidId; - function _setStatusCode(status) { - this._statusCode = status; - //update status msg - this._statusMessage = this._getStatus(); - } - //returns the size of the bid creative. Concatenation of width and height by ‘x’. - this.getSize = function() { - return this.width + 'x' + this.height; - }; + function _getStatus() { + switch (_statusCode) { + case 0: + return 'Pending'; + case 1: + return 'Bid available'; + case 2: + return 'Bid returned empty or error response'; + case 3: + return 'Bid timed out'; + } + } + + this.getStatusCode = function () { + return _statusCode; + }; + + //returns the size of the bid creative. Concatenation of width and height by ‘x’. + this.getSize = function () { + return this.width + 'x' + this.height; + }; } // Bid factory function. -exports.createBid = function(statusCde) { - return new Bid(statusCde); +exports.createBid = function (statusCde) { + return new Bid(statusCde); }; - -//module.exports = Bid; \ No newline at end of file diff --git a/src/bidmanager.js b/src/bidmanager.js index 09ddb7604c0..fd998e9bb42 100644 --- a/src/bidmanager.js +++ b/src/bidmanager.js @@ -1,6 +1,5 @@ var CONSTANTS = require('./constants.json'); var utils = require('./utils.js'); -var adaptermanager = require('./adaptermanager'); var events = require('./events'); var objectType_function = 'function'; @@ -33,359 +32,347 @@ var _callbackExecuted = false; var defaultBidderSettingsMap = {}; var bidderStartTimes = {}; -exports.getPlacementIdByCBIdentifer = function(id) { - return pbCallbackMap[id]; +exports.getPlacementIdByCBIdentifer = function (id) { + return pbCallbackMap[id]; }; - -exports.getBidResponseByAdUnit = function(adUnitCode) { - return pbBidResponseByPlacement; +exports.getBidResponseByAdUnit = function () { + return pbBidResponseByPlacement; }; +exports.clearAllBidResponses = function () { + _allBidsAvailable = false; + _callbackExecuted = false; + + //init bid response received count + initbidResponseReceivedCount(); -exports.clearAllBidResponses = function(adUnitCode) { - _allBidsAvailable = false; - _callbackExecuted = false; + //init expected bids count + initExpectedBidsCount(); - //init bid response received count - initbidResponseReceivedCount(); - //init expected bids count - initExpectedBidsCount(); - //clear the callback handler flag - externalCallbackArr.called = false; + //clear the callback handler flag + externalCallbackArr.called = false; - for (var prop in this.pbBidResponseByPlacement) { - delete this.pbBidResponseByPlacement[prop]; - } + for (var prop in this.pbBidResponseByPlacement) { + delete this.pbBidResponseByPlacement[prop]; + } }; /** * Returns a list of bidders that we haven't received a response yet * @return {array} [description] */ -exports.getTimedOutBidders = function(){ - var bidderArr = []; - utils._each(bidResponseReceivedCount,function(count,bidderCode){ - if(count === 0){ - bidderArr.push(bidderCode); - } - }); - - return bidderArr; +exports.getTimedOutBidders = function () { + return utils._map(bidResponseReceivedCount, function (count, bidderCode) { + if (count === 0) { + return bidderCode; + } + }); }; -function initbidResponseReceivedCount(){ - - bidResponseReceivedCount = {}; - - for(var i=0; i https://developers.google.com/analytics/devguides/collection/ios/v3/limits-quotas?hl=en - _eventCount = 0, - //limit data sent by leaving this false - _enableDistribution = false, - _timedOutBidders = []; - +var _disibleInteraction = { nonInteraction: true }; +var _analyticsQueue = []; +var _gaGlobal = null; +var _enableCheck = true; +var _category = 'Prebid.js Bids'; +var _eventCount = 0; +var _enableDistribution = false; +var _timedOutBidders = []; /** * This will enable sending data to google analytics. Only call once, or duplicate data will be sent! * @param {object} gaOptions to set distribution and GA global (if renamed); * @return {[type]} [description] */ -exports.enableAnalytics = function(gaOptions) { - if(typeof gaOptions.global !== 'undefined'){ - _gaGlobal = gaOptions.global; - } - else{ - //default global is window.ga - _gaGlobal = 'ga'; - } - if(typeof gaOptions.enableDistribution !== 'undefined'){ - _enableDistribution = gaOptions.enableDistribution; - } - - var bid = null; - - //first send all events fired before enableAnalytics called - - var existingEvents = events.getEvents(); - utils._each(existingEvents, function(eventObj) { - var args = eventObj.args; - if (!eventObj) { - return; - } - if (eventObj.eventType === BID_REQUESTED) { - //bid is 1st args - bid = args[0]; - sendBidRequestToGa(bid); - } else if (eventObj.eventType === BID_RESPONSE) { - //bid is 2nd args - bid = args[1]; - sendBidResponseToGa(bid); - - } else if (eventObj.eventType === BID_TIMEOUT) { - var bidderArray = args[0]; - _timedOutBidders = bidderArray; - //todo disable event listeners - - } else if (eventObj.eventType === BID_WON) { - bid = args[0]; - sendBidWonToGa(bid); - } - }); - - //Next register event listeners to send data immediately - - //bidRequests - events.on(BID_REQUESTED, function(bidRequestObj) { - sendBidRequestToGa(bidRequestObj); - }); - - //bidResponses - events.on(BID_RESPONSE, function(adunit, bid) { - sendBidResponseToGa(bid); - sendBidTimeouts(bid); - }); - - //bidTimeouts - events.on(BID_TIMEOUT, function(bidderArray) { - _timedOutBidders = bidderArray; - }); - - //wins - events.on(BID_WON, function(bid) { - sendBidWonToGa(bid); - }); +exports.enableAnalytics = function (gaOptions) { + if (typeof gaOptions.global !== 'undefined') { + _gaGlobal = gaOptions.global; + } else { + //default global is window.ga + _gaGlobal = 'ga'; + } + + if (typeof gaOptions.enableDistribution !== 'undefined') { + _enableDistribution = gaOptions.enableDistribution; + } + + var bid = null; + + //first send all events fired before enableAnalytics called + + var existingEvents = events.getEvents(); + utils._each(existingEvents, function (eventObj) { + var args = eventObj.args; + if (!eventObj) { + return; + } + + if (eventObj.eventType === BID_REQUESTED) { + //bid is 1st args + bid = args[0]; + sendBidRequestToGa(bid); + } else if (eventObj.eventType === BID_RESPONSE) { + //bid is 2nd args + bid = args[1]; + sendBidResponseToGa(bid); + + } else if (eventObj.eventType === BID_TIMEOUT) { + _timedOutBidders = args[0]; + } else if (eventObj.eventType === BID_WON) { + bid = args[0]; + sendBidWonToGa(bid); + } + }); + + //Next register event listeners to send data immediately + + //bidRequests + events.on(BID_REQUESTED, function (bidRequestObj) { + sendBidRequestToGa(bidRequestObj); + }); + + //bidResponses + events.on(BID_RESPONSE, function (adunit, bid) { + sendBidResponseToGa(bid); + sendBidTimeouts(bid); + }); + + //bidTimeouts + events.on(BID_TIMEOUT, function (bidderArray) { + _timedOutBidders = bidderArray; + }); + + //wins + events.on(BID_WON, function (bid) { + sendBidWonToGa(bid); + }); }; /** * Check if gaGlobal or window.ga is defined on page. If defined execute all the GA commands */ function checkAnalytics() { - if (_enableCheck && typeof window[_gaGlobal] === 'function' ) { - - for (var i = 0; i < _analyticsQueue.length; i++) { - _analyticsQueue[i].call(); - } - //override push to execute the command immediately from now on - _analyticsQueue.push = function(fn) { - fn.call(); - }; - //turn check into NOOP - _enableCheck = false; - } - utils.logMessage('event count sent to GA: ' + _eventCount); -} + if (_enableCheck && typeof window[_gaGlobal] === 'function') { + + for (var i = 0; i < _analyticsQueue.length; i++) { + _analyticsQueue[i].call(); + } + + //override push to execute the command immediately from now on + _analyticsQueue.push = function (fn) { + fn.call(); + }; + //turn check into NOOP + _enableCheck = false; + } + + utils.logMessage('event count sent to GA: ' + _eventCount); +} function convertToCents(dollars) { - if (dollars) { - return Math.floor(dollars * 100); - } - return 0; + if (dollars) { + return Math.floor(dollars * 100); + } + + return 0; } function getLoadTimeDistribution(time) { - var distribution; - if (time >= 0 && time < 200) { - distribution = '0-200ms'; - } else if (time >= 200 && time < 300) { - distribution = '200-300ms'; - } else if (time >= 300 && time < 400) { - distribution = '300-400ms'; - } else if (time >= 400 && time < 500) { - distribution = '400-500ms'; - } else if (time >= 500 && time < 600) { - distribution = '500-600ms'; - } else if (time >= 600 && time < 800) { - distribution = '600-800ms'; - } else if (time >= 800 && time < 1000) { - distribution = '800-1000ms'; - } else if (time >= 1000 && time < 1200) { - distribution = '1000-1200ms'; - } else if (time >= 1200 && time < 1500) { - distribution = '1200-1500ms'; - } else if (time >= 1500 && time < 2000) { - distribution = '1500-2000ms'; - } else if (time >= 2000) { - distribution = '2000ms above'; - } - - return distribution; + var distribution; + if (time >= 0 && time < 200) { + distribution = '0-200ms'; + } else if (time >= 200 && time < 300) { + distribution = '200-300ms'; + } else if (time >= 300 && time < 400) { + distribution = '300-400ms'; + } else if (time >= 400 && time < 500) { + distribution = '400-500ms'; + } else if (time >= 500 && time < 600) { + distribution = '500-600ms'; + } else if (time >= 600 && time < 800) { + distribution = '600-800ms'; + } else if (time >= 800 && time < 1000) { + distribution = '800-1000ms'; + } else if (time >= 1000 && time < 1200) { + distribution = '1000-1200ms'; + } else if (time >= 1200 && time < 1500) { + distribution = '1200-1500ms'; + } else if (time >= 1500 && time < 2000) { + distribution = '1500-2000ms'; + } else if (time >= 2000) { + distribution = '2000ms above'; + } + + return distribution; } - function getCpmDistribution(cpm) { - var distribution; - if (cpm >= 0 && cpm < 0.5) { - distribution = '$0-0.5'; - } else if (cpm >= 0.5 && cpm < 1) { - distribution = '$0.5-1'; - } else if (cpm >= 1 && cpm < 1.5) { - distribution = '$1-1.5'; - } else if (cpm >= 1.5 && cpm < 2) { - distribution = '$1.5-2'; - } else if (cpm >= 2 && cpm < 2.5) { - distribution = '$2-2.5'; - } else if (cpm >= 2.5 && cpm < 3) { - distribution = '$2.5-3'; - } else if (cpm >= 3 && cpm < 4) { - distribution = '$3-4'; - } else if (cpm >= 4 && cpm < 6) { - distribution = '$4-6'; - } else if (cpm >= 6 && cpm < 8) { - distribution = '$6-8'; - } else if (cpm >= 8) { - distribution = '$8 above'; - } - return distribution; + var distribution; + if (cpm >= 0 && cpm < 0.5) { + distribution = '$0-0.5'; + } else if (cpm >= 0.5 && cpm < 1) { + distribution = '$0.5-1'; + } else if (cpm >= 1 && cpm < 1.5) { + distribution = '$1-1.5'; + } else if (cpm >= 1.5 && cpm < 2) { + distribution = '$1.5-2'; + } else if (cpm >= 2 && cpm < 2.5) { + distribution = '$2-2.5'; + } else if (cpm >= 2.5 && cpm < 3) { + distribution = '$2.5-3'; + } else if (cpm >= 3 && cpm < 4) { + distribution = '$3-4'; + } else if (cpm >= 4 && cpm < 6) { + distribution = '$4-6'; + } else if (cpm >= 6 && cpm < 8) { + distribution = '$6-8'; + } else if (cpm >= 8) { + distribution = '$8 above'; + } + + return distribution; } - - function sendBidRequestToGa(bid) { - if (bid && bid.bidderCode) { - _analyticsQueue.push(function() { - _eventCount++; - window[_gaGlobal]('send', 'event', _category, 'Requests', bid.bidderCode, 1, _disibleInteraction); - }); - } - //check the queue - checkAnalytics(); + if (bid && bid.bidderCode) { + _analyticsQueue.push(function () { + _eventCount++; + window[_gaGlobal]('send', 'event', _category, 'Requests', bid.bidderCode, 1, _disibleInteraction); + }); + } + + //check the queue + checkAnalytics(); } - function sendBidResponseToGa(bid) { - if (bid && bid.bidderCode) { - _analyticsQueue.push(function() { - var cpmCents = convertToCents(bid.cpm), - bidder = bid.bidderCode; - if (typeof bid.timeToRespond !== 'undefined' && _enableDistribution) { - _eventCount++; - var dis = getLoadTimeDistribution(bid.timeToRespond); - window[_gaGlobal]('send', 'event', 'Prebid.js Load Time Distribution', dis, bidder, 1, _disibleInteraction); - } - if (bid.cpm > 0) { - _eventCount = _eventCount + 2; - var cpmDis = getCpmDistribution(bid.cpm); - if(_enableDistribution){ - _eventCount++; - window[_gaGlobal]('send', 'event', 'Prebid.js CPM Distribution', cpmDis, bidder, 1, _disibleInteraction); - } - window[_gaGlobal]('send', 'event', _category, 'Bids', bidder, cpmCents, _disibleInteraction); - window[_gaGlobal]('send', 'event', _category, 'Bid Load Time', bidder, bid.timeToRespond, _disibleInteraction); - } - }); - } - //check the queue - checkAnalytics(); + if (bid && bid.bidderCode) { + _analyticsQueue.push(function () { + var cpmCents = convertToCents(bid.cpm); + var bidder = bid.bidderCode; + if (typeof bid.timeToRespond !== 'undefined' && _enableDistribution) { + _eventCount++; + var dis = getLoadTimeDistribution(bid.timeToRespond); + window[_gaGlobal]('send', 'event', 'Prebid.js Load Time Distribution', dis, bidder, 1, _disibleInteraction); + } + + if (bid.cpm > 0) { + _eventCount = _eventCount + 2; + var cpmDis = getCpmDistribution(bid.cpm); + if (_enableDistribution) { + _eventCount++; + window[_gaGlobal]('send', 'event', 'Prebid.js CPM Distribution', cpmDis, bidder, 1, _disibleInteraction); + } + + window[_gaGlobal]('send', 'event', _category, 'Bids', bidder, cpmCents, _disibleInteraction); + window[_gaGlobal]('send', 'event', _category, 'Bid Load Time', bidder, bid.timeToRespond, _disibleInteraction); + } + }); + } + + //check the queue + checkAnalytics(); } -function sendBidTimeouts(bid){ - - if(bid && bid.bidder){ - _analyticsQueue.push(function(){ - utils._each(_timedOutBidders, function(bidderCode){ - if(bid.bidder === bidderCode){ - _eventCount++; - window[_gaGlobal]('send', 'event', _category, 'Timeouts', bidderCode, bid.timeToRespond, _disibleInteraction); - } - }); - }); - } - checkAnalytics(); +function sendBidTimeouts(bid) { + + if (bid && bid.bidder) { + _analyticsQueue.push(function () { + utils._each(_timedOutBidders, function (bidderCode) { + if (bid.bidder === bidderCode) { + _eventCount++; + window[_gaGlobal]('send', 'event', _category, 'Timeouts', bidderCode, bid.timeToRespond, _disibleInteraction); + } + }); + }); + } + + checkAnalytics(); } function sendBidWonToGa(bid) { - var cpmCents = convertToCents(bid.cpm); - _analyticsQueue.push(function() { - _eventCount++; - window[_gaGlobal]('send', 'event', _category, 'Wins', bid.bidderCode, cpmCents, _disibleInteraction); - }); - checkAnalytics(); + var cpmCents = convertToCents(bid.cpm); + _analyticsQueue.push(function () { + _eventCount++; + window[_gaGlobal]('send', 'event', _category, 'Wins', bid.bidderCode, cpmCents, _disibleInteraction); + }); + + checkAnalytics(); } diff --git a/src/polyfills.js b/src/polyfills.js index 6c3e29cf43f..56288172038 100644 --- a/src/polyfills.js +++ b/src/polyfills.js @@ -7,64 +7,68 @@ * @param fromIndex * @returns {*} */ -exports.indexOf = function(searchElement, fromIndex) { +exports.indexOf = function (searchElement, fromIndex) { - var k; + var len; + var k; - // 1. Let o be the result of calling ToObject passing - // the this value as the argument. - if (this == null) { - throw new TypeError('"this" is null or not defined'); - } + // 1. Let o be the result of calling ToObject passing + // the this value as the argument. + if (this === null) { + throw new TypeError('"this" is null or not defined'); + } - var o = Object(this); + var o = Object(this); - // 2. Let lenValue be the result of calling the Get - // internal method of o with the argument "length". - // 3. Let len be ToUint32(lenValue). - var len = o.length >>> 0; + // 2. Let lenValue be the result of calling the Get + // internal method of o with the argument "length". + // 3. Let len be ToUint32(lenValue). - // 4. If len is 0, return -1. - if (len === 0) { - return -1; - } + len = o.length >>> 0; - // 5. If argument fromIndex was passed let n be - // ToInteger(fromIndex); else let n be 0. - var n = +fromIndex || 0; + // 4. If len is 0, return -1. + if (len === 0) { + return -1; + } - if (Math.abs(n) === Infinity) { - n = 0; - } + // 5. If argument fromIndex was passed let n be + // ToInteger(fromIndex); else let n be 0. + var n = +fromIndex || 0; - // 6. If n >= len, return -1. - if (n >= len) { - return -1; - } + if (Math.abs(n) === Infinity) { + n = 0; + } - // 7. If n >= 0, then Let k be n. - // 8. Else, n<0, Let k be len - abs(n). - // If k is less than 0, then let k be 0. - k = Math.max(n >= 0 ? n : len - Math.abs(n), 0); + // 6. If n >= len, return -1. + if (n >= len) { + return -1; + } + + // 7. If n >= 0, then Let k be n. + // 8. Else, n<0, Let k be len - abs(n). + // If k is less than 0, then let k be 0. + k = Math.max(n >= 0 ? n : len - Math.abs(n), 0); - // 9. Repeat, while k < len - while (k < len) { - // a. Let Pk be ToString(k). - // This is implicit for LHS operands of the in operator - // b. Let kPresent be the result of calling the - // HasProperty internal method of o with argument Pk. - // This step can be combined with c - // c. If kPresent is true, then - // i. Let elementK be the result of calling the Get - // internal method of o with the argument ToString(k). - // ii. Let same be the result of applying the - // Strict Equality Comparison Algorithm to - // searchElement and elementK. - // iii. If same is true, return k. - if (k in o && o[k] === searchElement) { - return k; - } - k++; + // 9. Repeat, while k < len + while (k < len) { + // a. Let Pk be ToString(k). + // This is implicit for LHS operands of the in operator + // b. Let kPresent be the result of calling the + // HasProperty internal method of o with argument Pk. + // This step can be combined with c + // c. If kPresent is true, then + // i. Let elementK be the result of calling the Get + // internal method of o with the argument ToString(k). + // ii. Let same be the result of applying the + // Strict Equality Comparison Algorithm to + // searchElement and elementK. + // iii. If same is true, return k. + if (k in o && o[k] === searchElement) { + return k; } - return -1; + + k++; + } + + return -1; }; diff --git a/src/prebid.js b/src/prebid.js index 4f40fa27468..31cd73c665e 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -1,4 +1,5 @@ /** @module pbjs */ + // if pbjs already exists in global dodcument scope, use it, if not, create the object window.pbjs = (window.pbjs || {}); window.pbjs.que = window.pbjs.que || []; @@ -21,18 +22,19 @@ var objectType_string = 'string'; var BID_WON = CONSTANTS.EVENTS.BID_WON; var BID_TIMEOUT = CONSTANTS.EVENTS.BID_TIMEOUT; -var pb_preBidders = [], - pb_placements = [], - pb_bidderMap = {}, - pb_targetingMap = {}, - pb_keyHistoryMap = {}, - pb_bidsTimedOut = false; +var pb_preBidders = []; +var pb_placements = []; +var pb_bidderMap = {}; +var pb_targetingMap = {}; +var pb_keyHistoryMap = {}; +var pb_bidsTimedOut = false; var eventValidators = { - 'bidWon': checkDefinedPlacement + bidWon: checkDefinedPlacement }; /* Public vars */ + //default timeout for all bids pbjs.bidderTimeout = pbjs.bidderTimeout || 3000; pbjs.logging = pbjs.logging || false; @@ -48,297 +50,295 @@ pbjs.adUnits = pbjs.adUnits || []; * @param {function} cmd Annoymous function to execute * @alias module:pbjs.que.push */ -pbjs.que.push = function(cmd) { - if (typeof cmd === objectType_function) { - try { - cmd.call(); - } catch (e) { - utils.logError('Error processing command :' + e.message); - } - } else { - utils.logError('Commands written into pbjs.que.push must wrapped in a function'); - } +pbjs.que.push = function (cmd) { + if (typeof cmd === objectType_function) { + try { + cmd.call(); + } catch (e) { + utils.logError('Error processing command :' + e.message); + } + } else { + utils.logError('Commands written into pbjs.que.push must wrapped in a function'); + } }; function processQue() { - for (var i = 0; i < pbjs.que.length; i++) { - if (typeof pbjs.que[i].called === objectType_undefined) { - try{ - pbjs.que[i].call(); - pbjs.que[i].called = true; - } - catch(e){ - utils.logError('Error processing command :', 'prebid.js', e); - } - - } - } + for (var i = 0; i < pbjs.que.length; i++) { + if (typeof pbjs.que[i].called === objectType_undefined) { + try { + pbjs.que[i].call(); + pbjs.que[i].called = true; + } + catch (e) { + utils.logError('Error processing command :', 'prebid.js', e); + } + } + } } /* * Main method entry point method */ function init(timeout, adUnitCodeArr) { - var cbTimeout = 0; - if(typeof timeout === objectType_undefined || timeout === null){ - cbTimeout = pbjs.bidderTimeout; - } - else{ - cbTimeout = timeout; - } - - if (!isValidAdUnitSetting()) { - utils.logMessage('No adUnits configured. No bids requested.'); - return; - } - //set timeout for all bids - setTimeout(bidmanager.executeCallback, cbTimeout); - //parse settings into internal vars - if (adUnitCodeArr && utils.isArray(adUnitCodeArr)) { - for (var k = 0; k < adUnitCodeArr.length; k++) { - for (var i = 0; i < pbjs.adUnits.length; i++) { - if (pbjs.adUnits[i].code === adUnitCodeArr[k]) { - pb_placements.push(pbjs.adUnits[i]); - } - } - } - loadPreBidders(); - sortAndCallBids(); - } else { - pb_placements = pbjs.adUnits; - //Aggregrate prebidders by their codes - loadPreBidders(); - //sort and call // default no sort - sortAndCallBids(); - } + var cbTimeout = 0; + if (typeof timeout === objectType_undefined || timeout === null) { + cbTimeout = pbjs.bidderTimeout; + } else { + cbTimeout = timeout; + } + + if (!isValidAdUnitSetting()) { + utils.logMessage('No adUnits configured. No bids requested.'); + return; + } + + //set timeout for all bids + setTimeout(bidmanager.executeCallback, cbTimeout); + + //parse settings into internal vars + if (adUnitCodeArr && utils.isArray(adUnitCodeArr)) { + for (var k = 0; k < adUnitCodeArr.length; k++) { + for (var i = 0; i < pbjs.adUnits.length; i++) { + if (pbjs.adUnits[i].code === adUnitCodeArr[k]) { + pb_placements.push(pbjs.adUnits[i]); + } + } + } + + loadPreBidders(); + sortAndCallBids(); + } else { + pb_placements = pbjs.adUnits; + + //Aggregrate prebidders by their codes + loadPreBidders(); + + //sort and call // default no sort + sortAndCallBids(); + } } function isValidAdUnitSetting() { - if (pbjs.adUnits && pbjs.adUnits.length !== 0) { - return true; - } - return false; + return !!(pbjs.adUnits && pbjs.adUnits.length !== 0); + } -function timeOutBidders(){ - if(!pb_bidsTimedOut){ - pb_bidsTimedOut = true; - var timedOutBidders = bidmanager.getTimedOutBidders(); - events.emit(BID_TIMEOUT, timedOutBidders); - } +function timeOutBidders() { + if (!pb_bidsTimedOut) { + pb_bidsTimedOut = true; + var timedOutBidders = bidmanager.getTimedOutBidders(); + events.emit(BID_TIMEOUT, timedOutBidders); + } } function sortAndCallBids(sortFunc) { - //Translate the bidder map into array so we can sort later if wanted - var pbArr = utils._map(pb_bidderMap, function(v, k) { return v; }); + //Translate the bidder map into array so we can sort later if wanted + var pbArr = utils._map(pb_bidderMap, function (v) { + return v; + }); - if (typeof sortFunc === objectType_function) { - pbArr.sort(sortFunc); - } - adaptermanager.callBids(pbArr); + if (typeof sortFunc === objectType_function) { + pbArr.sort(sortFunc); + } + + adaptermanager.callBids(pbArr); } function loadPreBidders() { - for (var i = 0; i < pb_placements.length; i++) { - var bids = pb_placements[i][CONSTANTS.JSON_MAPPING.PL_BIDS]; - var placementCode = pb_placements[i][CONSTANTS.JSON_MAPPING.PL_CODE]; - storeBidRequestByBidder(pb_placements[i][CONSTANTS.JSON_MAPPING.PL_CODE], pb_placements[i][CONSTANTS.JSON_MAPPING.PL_SIZE], bids); - //store pending response by placement - bidmanager.addBidResponse(placementCode); - } + for (var i = 0; i < pb_placements.length; i++) { + var bids = pb_placements[i][CONSTANTS.JSON_MAPPING.PL_BIDS]; + var placementCode = pb_placements[i][CONSTANTS.JSON_MAPPING.PL_CODE]; + storeBidRequestByBidder(pb_placements[i][CONSTANTS.JSON_MAPPING.PL_CODE], pb_placements[i][CONSTANTS.JSON_MAPPING.PL_SIZE], bids); - for (i = 0; i < pb_preBidders.length; i++) { - pb_preBidders[i].loadPreBid(); - } - //send a reference to bidmanager - bidmanager.setBidderMap(pb_bidderMap); -} - -function storeBidRequestByBidder(placementCode, sizes, bids) { - for (var i = 0; i < bids.length; i++) { - - var currentBid = bids[i]; - currentBid.placementCode = placementCode; - currentBid.sizes = sizes; - - //look up bidder on map - var bidderName = bids[i][CONSTANTS.JSON_MAPPING.BD_BIDDER]; - var bidderObj = pb_bidderMap[bidderName]; - if (typeof bidderObj === objectType_undefined) { - //bid not found - var partnerBids = { - bidderCode: bidderName, - bids: [] - }; - partnerBids.bids.push(currentBid); - //put bidder on map with bids - pb_bidderMap[bidderName] = partnerBids; - } else { - bidderObj.bids.push(currentBid); - } + //store pending response by placement + bidmanager.addBidResponse(placementCode); + } - } -} + for (i = 0; i < pb_preBidders.length; i++) { + pb_preBidders[i].loadPreBid(); + } -//use in place of hasOwnPropery - as opposed to a polyfill -function hasOwn(o, p) { - if (o.hasOwnProperty) { - return o.hasOwnProperty(p); - } else { - return (typeof o[p] !== objectType_undefined) && (o.constructor.prototype[p] !== o[p]); - } + //send a reference to bidmanager + bidmanager.setBidderMap(pb_bidderMap); } -function isEmptyObject(obj) { - var name; - for (name in obj) { - return false; - } - return true; +function storeBidRequestByBidder(placementCode, sizes, bids) { + for (var i = 0; i < bids.length; i++) { + + var currentBid = bids[i]; + currentBid.placementCode = placementCode; + currentBid.sizes = sizes; + + //look up bidder on map + var bidderName = bids[i][CONSTANTS.JSON_MAPPING.BD_BIDDER]; + var bidderObj = pb_bidderMap[bidderName]; + if (typeof bidderObj === objectType_undefined) { + //bid not found + var partnerBids = { + bidderCode: bidderName, + bids: [] + }; + partnerBids.bids.push(currentBid); + + //put bidder on map with bids + pb_bidderMap[bidderName] = partnerBids; + } else { + bidderObj.bids.push(currentBid); + } + + } } function getWinningBid(bidArray) { - var winningBid = {}; - if (bidArray && bidArray.length !== 0) { - bidArray.sort(function(a, b) { - //put the highest CPM first - return b.cpm - a.cpm; - }); - //the first item has the highest cpm - winningBid = bidArray[0]; - //TODO : if winning bid CPM === 0 - we need to indicate no targeting should be set - } - return winningBid.bid; + var winningBid = {}; + if (bidArray && bidArray.length !== 0) { + bidArray.sort(function (a, b) { + //put the highest CPM first + return b.cpm - a.cpm; + }); -} + //the first item has the highest cpm + winningBid = bidArray[0]; + //TODO : if winning bid CPM === 0 - we need to indicate no targeting should be set + } + + return winningBid.bid; +} function setGPTAsyncTargeting(code, slot) { - //get the targeting that is already configured - var keyStrings = getTargetingfromGPTIdentifier(slot); - //copy keyStrings into pb_keyHistoryMap by code - if(!pb_keyHistoryMap[code]){ - pb_keyHistoryMap[code] = keyStrings; - } - else{ - utils.extend(pb_keyHistoryMap[code], keyStrings); - } - utils._each(pb_keyHistoryMap[code], function(value, key){ - //since DFP doesn't support deleting a single key, we will set all to empty string - //This is "clear" for that key - slot.setTargeting(key, ''); - //utils.logMessage('Attempting to clear the key : ' + key + ' to empty string for code: ' + code); - }); - for (var key in keyStrings) { - if (keyStrings.hasOwnProperty(key)) { - try { - utils.logMessage('Attempting to set key value for slot: ' + slot.getSlotElementId() + ' key: ' + key + ' value: ' + encodeURIComponent(keyStrings[key])); - slot.setTargeting(key, keyStrings[key]); - } catch (e) { - utils.logMessage('Problem setting key value pairs in slot: ' + e.message); - } - } - } + //get the targeting that is already configured + var keyStrings = getTargetingfromGPTIdentifier(slot); + + //copy keyStrings into pb_keyHistoryMap by code + if (!pb_keyHistoryMap[code]) { + pb_keyHistoryMap[code] = keyStrings; + } else { + utils.extend(pb_keyHistoryMap[code], keyStrings); + } + + utils._each(pb_keyHistoryMap[code], function (value, key) { + //since DFP doesn't support deleting a single key, we will set all to empty string + //This is "clear" for that key + slot.setTargeting(key, ''); + + //utils.logMessage('Attempting to clear the key : ' + key + ' to empty string for code: ' + code); + }); + + for (var key in keyStrings) { + if (keyStrings.hasOwnProperty(key)) { + try { + utils.logMessage('Attempting to set key value for slot: ' + slot.getSlotElementId() + ' key: ' + key + ' value: ' + encodeURIComponent(keyStrings[key])); + slot.setTargeting(key, keyStrings[key]); + } catch (e) { + utils.logMessage('Problem setting key value pairs in slot: ' + e.message); + } + } + } } /* * This function returns the object map of placements or * if placement code is specified return just 1 placement bids */ function getBidResponsesByAdUnit(adunitCode) { - var returnObj = {}; - if (adunitCode) { - returnObj = bidmanager.pbBidResponseByPlacement[adunitCode]; - return returnObj; - } - else { - return bidmanager.pbBidResponseByPlacement; - } + var returnObj = {}; + if (adunitCode) { + returnObj = bidmanager.pbBidResponseByPlacement[adunitCode]; + return returnObj; + } else { + return bidmanager.pbBidResponseByPlacement; + } } - /* * Copies bids into a bidArray response */ function buildBidResponse(bidArray) { - var bidResponseArray = []; - var adUnitCode = ''; - //temp array to hold auction for bids - var bidArrayTargeting = []; - var bidClone = {}; - if (bidArray && bidArray[0] && bidArray[0].adUnitCode) { - // init the pb_targetingMap for the adUnitCode - adUnitCode = bidArray[0] && bidArray[0].adUnitCode; - pb_targetingMap[adUnitCode] = {}; - for (var i = 0; i < bidArray.length; i++) { - var bid = bidArray[i]; - //clone by json parse. This also gets rid of unwanted function properties - bidClone = getCloneBid(bid); - - if (bid.alwaysUseBid && bidClone.adserverTargeting) { // add the bid if alwaysUse and bid has returned - // push key into targeting - pb_targetingMap[bidClone.adUnitCode] = utils.extend(pb_targetingMap[bidClone.adUnitCode], bidClone.adserverTargeting); - } else if (bid.cpm && bid.cpm > 0){ - //else put into auction array if cpm > 0 - bidArrayTargeting.push({ - cpm: bid.cpm, - bid: bid - }); - } - //put all bids into bidArray by default - bidResponseArray.push(bidClone); - } - } - - // push the winning bid into targeting map - if (adUnitCode && bidArrayTargeting.length !== 0) { - var winningBid = getWinningBid(bidArrayTargeting); - var keyValues = winningBid.adserverTargeting; - pb_targetingMap[adUnitCode] = utils.extend(pb_targetingMap[adUnitCode], keyValues); - } - - return bidResponseArray; + var bidResponseArray = []; + var adUnitCode = ''; + + //temp array to hold auction for bids + var bidArrayTargeting = []; + var bidClone = {}; + if (bidArray && bidArray[0] && bidArray[0].adUnitCode) { + // init the pb_targetingMap for the adUnitCode + adUnitCode = bidArray[0] && bidArray[0].adUnitCode; + pb_targetingMap[adUnitCode] = {}; + for (var i = 0; i < bidArray.length; i++) { + var bid = bidArray[i]; + + //clone by json parse. This also gets rid of unwanted function properties + bidClone = getCloneBid(bid); + + if (bid.alwaysUseBid && bidClone.adserverTargeting) { // add the bid if alwaysUse and bid has returned + // push key into targeting + pb_targetingMap[bidClone.adUnitCode] = utils.extend(pb_targetingMap[bidClone.adUnitCode], bidClone.adserverTargeting); + } else if (bid.cpm && bid.cpm > 0) { + //else put into auction array if cpm > 0 + bidArrayTargeting.push({ + cpm: bid.cpm, + bid: bid + }); + } + + //put all bids into bidArray by default + bidResponseArray.push(bidClone); + } + } + + // push the winning bid into targeting map + if (adUnitCode && bidArrayTargeting.length !== 0) { + var winningBid = getWinningBid(bidArrayTargeting); + var keyValues = winningBid.adserverTargeting; + pb_targetingMap[adUnitCode] = utils.extend(pb_targetingMap[adUnitCode], keyValues); + } + + return bidResponseArray; } function getCloneBid(bid) { - var bidClone = {}; - //clone by json parse. This also gets rid of unwanted function properties - if (bid) { - var jsonBid = JSON.stringify(bid); - bidClone = JSON.parse(jsonBid); - - //clean up bid clone - delete bidClone.pbLg; - delete bidClone.pbMg; - delete bidClone.pbHg; - } - return bidClone; + var bidClone = {}; + + //clone by json parse. This also gets rid of unwanted function properties + if (bid) { + var jsonBid = JSON.stringify(bid); + bidClone = JSON.parse(jsonBid); + + //clean up bid clone + delete bidClone.pbLg; + delete bidClone.pbMg; + delete bidClone.pbHg; + } + + return bidClone; } function resetBids() { - bidmanager.clearAllBidResponses(); - pb_bidderMap = {}; - pb_placements = []; - pb_targetingMap = {}; - pb_bidsTimedOut = false; + bidmanager.clearAllBidResponses(); + pb_bidderMap = {}; + pb_placements = []; + pb_targetingMap = {}; + pb_bidsTimedOut = false; } -function requestAllBids(tmout){ - var timeout = tmout; - resetBids(); - init(timeout); +function requestAllBids(tmout) { + var timeout = tmout; + resetBids(); + init(timeout); } function checkDefinedPlacement(id) { - var placementCodes = utils._map(pb_placements, function (placement) { - return placement.code; - }); + var placementCodes = utils._map(pb_placements, function (placement) { + return placement.code; + }); - if (!utils.contains(placementCodes, id)) { - utils.logError('The "' + id + '" placement is not defined.'); - return; - } - return true; + if (!utils.contains(placementCodes, id)) { + utils.logError('The "' + id + '" placement is not defined.'); + return; + } + + return true; } ////////////////////////////////// @@ -350,40 +350,40 @@ function checkDefinedPlacement(id) { * This function returns the query string targeting parameters available at this moment for a given ad unit. Note that some bidder's response may not have been received if you call this function too quickly after the requests are sent. * @param {string} [adunitCode] adUnitCode to get the bid responses for * @alias module:pbjs.getAdserverTargetingForAdUnitCodeStr - * @return {array} returnObj return bids array + * @return {array} returnObj return bids array */ -pbjs.getAdserverTargetingForAdUnitCodeStr = function(adunitCode) { - // call to retrieve bids array - if(adunitCode){ - var res = pbjs.getAdserverTargetingForAdUnitCode(adunitCode); - return utils.transformAdServerTargetingObj(res); - } - else{ - utils.logMessage('Need to call getAdserverTargetingForAdUnitCodeStr with adunitCode'); - } +pbjs.getAdserverTargetingForAdUnitCodeStr = function (adunitCode) { + // call to retrieve bids array + if (adunitCode) { + var res = pbjs.getAdserverTargetingForAdUnitCode(adunitCode); + return utils.transformAdServerTargetingObj(res); + } else { + utils.logMessage('Need to call getAdserverTargetingForAdUnitCodeStr with adunitCode'); + } }; /** * This function returns the query string targeting parameters available at this moment for a given ad unit. Note that some bidder's response may not have been received if you call this function too quickly after the requests are sent. * @param {string} [adunitCode] adUnitCode to get the bid responses for * @alias module:pbjs.getAdserverTargetingForAdUnitCode - * @return {object} returnObj return bids + * @return {object} returnObj return bids */ -pbjs.getAdserverTargetingForAdUnitCode = function(adunitCode) { - // call to populate pb_targetingMap - pbjs.getBidResponses(adunitCode); +pbjs.getAdserverTargetingForAdUnitCode = function (adunitCode) { + // call to populate pb_targetingMap + pbjs.getBidResponses(adunitCode); - if (adunitCode) { - return pb_targetingMap[adunitCode]; - } - return pb_targetingMap; + if (adunitCode) { + return pb_targetingMap[adunitCode]; + } + + return pb_targetingMap; }; /** * returns all ad server targeting for all ad units * @return {object} Map of adUnitCodes and targeting values [] * @alias module:pbjs.getAdserverTargeting */ -pbjs.getAdserverTargeting = function() { - return pbjs.getAdserverTargetingForAdUnitCode(); +pbjs.getAdserverTargeting = function () { + return pbjs.getAdserverTargetingForAdUnitCode(); }; /** @@ -392,40 +392,39 @@ pbjs.getAdserverTargeting = function() { * @alias module:pbjs.getBidResponses * @return {object} map | object that contains the bidResponses */ -pbjs.getBidResponses = function(adunitCode) { - var bidArrayTargeting = []; - var response = {}; - var bidArray = []; - var returnObj = {}; - - if (adunitCode) { - response = getBidResponsesByAdUnit(adunitCode); - bidArray = []; - if (response && response.bids) { - bidArray = buildBidResponse(response.bids); - } - - returnObj = { - bids: bidArray - }; - - } else { - response = getBidResponsesByAdUnit(); - for (var adUnit in response) { - if (response.hasOwnProperty(adUnit)) { - if (response && response[adUnit] && response[adUnit].bids) { - bidArray = buildBidResponse(response[adUnit].bids); - } - - returnObj[adUnit] = { - bids: bidArray - }; - - } - } - } - - return returnObj; +pbjs.getBidResponses = function (adunitCode) { + var response = {}; + var bidArray = []; + var returnObj = {}; + + if (adunitCode) { + response = getBidResponsesByAdUnit(adunitCode); + bidArray = []; + if (response && response.bids) { + bidArray = buildBidResponse(response.bids); + } + + returnObj = { + bids: bidArray + }; + + } else { + response = getBidResponsesByAdUnit(); + for (var adUnit in response) { + if (response.hasOwnProperty(adUnit)) { + if (response && response[adUnit] && response[adUnit].bids) { + bidArray = buildBidResponse(response[adUnit].bids); + } + + returnObj[adUnit] = { + bids: bidArray + }; + + } + } + } + + return returnObj; }; /** @@ -434,57 +433,60 @@ pbjs.getBidResponses = function(adunitCode) { * @alias module:pbjs.getBidResponsesForAdUnitCode * @return {Object} bidResponse object */ -pbjs.getBidResponsesForAdUnitCode = function(adUnitCode) { - return pbjs.getBidResponses(adUnitCode); +pbjs.getBidResponsesForAdUnitCode = function (adUnitCode) { + return pbjs.getBidResponses(adUnitCode); }; /** * Set query string targeting on adUnits specified. The logic for deciding query strings is described in the section Configure AdServer Targeting. Note that this function has to be called after all ad units on page are defined. * @param {array} [codeArr] an array of adUnitodes to set targeting for. * @alias module:pbjs.setTargetingForAdUnitsGPTAsync */ -pbjs.setTargetingForAdUnitsGPTAsync = function(codeArr) { - if (!window.googletag || !utils.isFn(window.googletag.pubads) || !utils.isFn(window.googletag.pubads().getSlots)) { - utils.logError('window.googletag is not defined on the page'); - return; - } - - //emit bid timeout event here - timeOutBidders(); - - var adUnitCodesArr = codeArr; - - if (typeof codeArr === objectType_string) { - adUnitCodesArr = [codeArr]; - } else if (typeof codeArr === objectType_object) { - adUnitCodesArr = codeArr; - } - - var placementBids = {}, - i = 0; - if (adUnitCodesArr) { - for (i = 0; i < adUnitCodesArr.length; i++) { - var code = adUnitCodesArr[i]; - //get all the slots from google tag - var slots = window.googletag.pubads().getSlots(); - for (var k = 0; k < slots.length; k++) { - - if (slots[k].getSlotElementId() === code || slots[k].getAdUnitPath() === code) { - placementBids = getBidResponsesByAdUnit(code); - setGPTAsyncTargeting(code, slots[k]); - } - } - } - } else { - //get all the slots from google tag - var slots = window.googletag.pubads().getSlots(); - for (i = 0; i < slots.length; i++) { - var adUnitCode = slots[i].getSlotElementId(); - if (adUnitCode) { - //placementBids = getBidsFromGTPIdentifier(slots[i]); - setGPTAsyncTargeting(adUnitCode, slots[i]); - } - } - } +pbjs.setTargetingForAdUnitsGPTAsync = function (codeArr) { + if (!window.googletag || !utils.isFn(window.googletag.pubads) || !utils.isFn(window.googletag.pubads().getSlots)) { + utils.logError('window.googletag is not defined on the page'); + return; + } + + //emit bid timeout event here + timeOutBidders(); + + var adUnitCodesArr = codeArr; + + if (typeof codeArr === objectType_string) { + adUnitCodesArr = [codeArr]; + } else if (typeof codeArr === objectType_object) { + adUnitCodesArr = codeArr; + } + + var placementBids = {}; + var i = 0; + var slots; + + if (adUnitCodesArr) { + for (i = 0; i < adUnitCodesArr.length; i++) { + var code = adUnitCodesArr[i]; + + //get all the slots from google tag + slots = window.googletag.pubads().getSlots(); + for (var k = 0; k < slots.length; k++) { + + if (slots[k].getSlotElementId() === code || slots[k].getAdUnitPath() === code) { + placementBids = getBidResponsesByAdUnit(code); + setGPTAsyncTargeting(code, slots[k]); + } + } + } + } else { + //get all the slots from google tag + slots = window.googletag.pubads().getSlots(); + for (i = 0; i < slots.length; i++) { + var adUnitCode = slots[i].getSlotElementId(); + if (adUnitCode) { + //placementBids = getBidsFromGTPIdentifier(slots[i]); + setGPTAsyncTargeting(adUnitCode, slots[i]); + } + } + } }; /** @@ -492,28 +494,30 @@ pbjs.setTargetingForAdUnitsGPTAsync = function(codeArr) { * @param {[type]} slot [description] * @return {[type]} [description] */ -function getTargetingfromGPTIdentifier(slot){ - var targeting = null; - if(slot){ - //first get by elementId - targeting = pbjs.getAdserverTargetingForAdUnitCode(slot.getSlotElementId()); - //if not available, try by adUnitPath - if(!targeting){ - targeting = pbjs.getAdserverTargetingForAdUnitCode(slot.getAdUnitPath()); - } - } - return targeting; +function getTargetingfromGPTIdentifier(slot) { + var targeting = null; + if (slot) { + //first get by elementId + targeting = pbjs.getAdserverTargetingForAdUnitCode(slot.getSlotElementId()); + + //if not available, try by adUnitPath + if (!targeting) { + targeting = pbjs.getAdserverTargetingForAdUnitCode(slot.getAdUnitPath()); + } + } + + return targeting; } /** -/** + /** * Set query string targeting on all GPT ad units. * @alias module:pbjs.setTargetingForGPTAsync */ -pbjs.setTargetingForGPTAsync = function(codeArr) { - pbjs.setTargetingForAdUnitsGPTAsync(codeArr); +pbjs.setTargetingForGPTAsync = function (codeArr) { + pbjs.setTargetingForAdUnitsGPTAsync(codeArr); }; /** @@ -521,8 +525,8 @@ pbjs.setTargetingForGPTAsync = function(codeArr) { * @alias module:pbjs.allBidsAvailable * @return {bool} all bids available */ -pbjs.allBidsAvailable = function() { - return bidmanager.allBidsBack(); +pbjs.allBidsAvailable = function () { + return bidmanager.allBidsBack(); }; /** @@ -531,79 +535,80 @@ pbjs.allBidsAvailable = function() { * @param {string} id bid id to locate the ad * @alias module:pbjs.renderAd */ -pbjs.renderAd = function(doc, id) { - utils.logMessage('Calling renderAd with adId :' + id); - if (doc && id) { - try { - //lookup ad by ad Id - var adObject = bidmanager._adResponsesByBidderId[id]; - if (adObject) { - //emit 'bid won' event here - events.emit(BID_WON, adObject); - var height = adObject.height; - var width = adObject.width; - var url = adObject.adUrl; - var ad = adObject.ad; - - if (ad) { - doc.write(ad); - doc.close(); - if (doc.defaultView && doc.defaultView.frameElement) { - doc.defaultView.frameElement.width = width; - doc.defaultView.frameElement.height = height; - } - } - //doc.body.style.width = width; - //doc.body.style.height = height; - else if (url) { - doc.write(''); - doc.close(); - - if (doc.defaultView && doc.defaultView.frameElement) { - doc.defaultView.frameElement.width = width; - doc.defaultView.frameElement.height = height; - } - - } else { - utils.logError('Error trying to write ad. No ad for bid response id: ' + id); - } - - } else { - utils.logError('Error trying to write ad. Cannot find ad by given id : ' + id); - } - - } catch (e) { - utils.logError('Error trying to write ad Id :' + id + ' to the page:' + e.message); - } - } else { - utils.logError('Error trying to write ad Id :' + id + ' to the page. Missing document or adId'); - } +pbjs.renderAd = function (doc, id) { + utils.logMessage('Calling renderAd with adId :' + id); + if (doc && id) { + try { + //lookup ad by ad Id + var adObject = bidmanager._adResponsesByBidderId[id]; + if (adObject) { + //emit 'bid won' event here + events.emit(BID_WON, adObject); + var height = adObject.height; + var width = adObject.width; + var url = adObject.adUrl; + var ad = adObject.ad; + + if (ad) { + doc.write(ad); + doc.close(); + if (doc.defaultView && doc.defaultView.frameElement) { + doc.defaultView.frameElement.width = width; + doc.defaultView.frameElement.height = height; + } + } + + //doc.body.style.width = width; + //doc.body.style.height = height; + else if (url) { + doc.write(''); + doc.close(); + + if (doc.defaultView && doc.defaultView.frameElement) { + doc.defaultView.frameElement.width = width; + doc.defaultView.frameElement.height = height; + } + + } else { + utils.logError('Error trying to write ad. No ad for bid response id: ' + id); + } + + } else { + utils.logError('Error trying to write ad. Cannot find ad by given id : ' + id); + } + + } catch (e) { + utils.logError('Error trying to write ad Id :' + id + ' to the page:' + e.message); + } + } else { + utils.logError('Error trying to write ad Id :' + id + ' to the page. Missing document or adId'); + } }; - /* * @deprecated - will be removed next release. Use pbjs.requestBids */ -pbjs.requestBidsForAdUnit = function(adUnitCode) { - resetBids(); - init(adUnitCode); +pbjs.requestBidsForAdUnit = function (adUnitCode) { + resetBids(); + init(adUnitCode); }; /** * @deprecated - will be removed next release. Use pbjs.requestBids */ -pbjs.requestBidsForAdUnits = function(adUnitsObj) { - if (!adUnitsObj || adUnitsObj.constructor !== Array) { - utils.logError('requestBidsForAdUnits must pass an array of adUnits'); - return; - } - resetBids(); - var adUnitBackup = pbjs.adUnits.slice(0); - pbjs.adUnits = adUnitsObj; - init(); - pbjs.adUnits = adUnitBackup; +pbjs.requestBidsForAdUnits = function (adUnitsObj) { + if (!adUnitsObj || adUnitsObj.constructor !== Array) { + utils.logError('requestBidsForAdUnits must pass an array of adUnits'); + return; + } + + resetBids(); + var adUnitBackup = pbjs.adUnits.slice(0); + pbjs.adUnits = adUnitsObj; + init(); + pbjs.adUnits = adUnitBackup; }; @@ -612,17 +617,16 @@ pbjs.requestBidsForAdUnits = function(adUnitsObj) { * @param {String} adUnitCode the adUnitCode to remove * @alias module:pbjs.removeAdUnit */ -pbjs.removeAdUnit = function(adUnitCode) { - if (adUnitCode) { - for (var i = 0; i < pbjs.adUnits.length; i++) { - if (pbjs.adUnits[i].code === adUnitCode) { - pbjs.adUnits.splice(i, 1); - } - } - } +pbjs.removeAdUnit = function (adUnitCode) { + if (adUnitCode) { + for (var i = 0; i < pbjs.adUnits.length; i++) { + if (pbjs.adUnits[i].code === adUnitCode) { + pbjs.adUnits.splice(i, 1); + } + } + } }; - /** * Request bids ad-hoc. This function does not add or remove adUnits already configured. * @param {Object} requestObj @@ -632,39 +636,38 @@ pbjs.removeAdUnit = function(adUnitCode) { * @param {function} [requestObj.bidsBackHandler] Callback to execute when all the bid responses are back or the timeout hits. * @alias module:pbjs.requestBids */ -pbjs.requestBids = function(requestObj) { - if (!requestObj) { - //utils.logMessage('requesting all bids'); - - requestAllBids(); - - } - else{ - var adUnitCodes = requestObj.adUnitCodes; - var adUnits = requestObj.adUnits; - var timeout = requestObj.timeout; - var bidsBackHandler = requestObj.bidsBackHandler; - var adUnitBackup = pbjs.adUnits.slice(0); - - if (typeof bidsBackHandler === objectType_function) { - bidmanager.addOneTimeCallback(bidsBackHandler); - } - - if (adUnitCodes && utils.isArray(adUnitCodes)) { - resetBids(); - init(timeout, adUnitCodes); - - } else if (adUnits && utils.isArray(adUnits)) { - resetBids(); - pbjs.adUnits = adUnits; - init(timeout); - } else { - //request all ads - requestAllBids(timeout); - } - - pbjs.adUnits = adUnitBackup; - } +pbjs.requestBids = function (requestObj) { + if (!requestObj) { + //utils.logMessage('requesting all bids'); + + requestAllBids(); + + } else { + var adUnitCodes = requestObj.adUnitCodes; + var adUnits = requestObj.adUnits; + var timeout = requestObj.timeout; + var bidsBackHandler = requestObj.bidsBackHandler; + var adUnitBackup = pbjs.adUnits.slice(0); + + if (typeof bidsBackHandler === objectType_function) { + bidmanager.addOneTimeCallback(bidsBackHandler); + } + + if (adUnitCodes && utils.isArray(adUnitCodes)) { + resetBids(); + init(timeout, adUnitCodes); + + } else if (adUnits && utils.isArray(adUnits)) { + resetBids(); + pbjs.adUnits = adUnits; + init(timeout); + } else { + //request all ads + requestAllBids(timeout); + } + + pbjs.adUnits = adUnitBackup; + } }; @@ -674,13 +677,13 @@ pbjs.requestBids = function(requestObj) { * @param {Array|String} adUnitArr Array of adUnits or single adUnit Object. * @alias module:pbjs.addAdUnits */ -pbjs.addAdUnits = function(adUnitArr) { - if (utils.isArray(adUnitArr)) { - //append array to existing - pbjs.adUnits.push.apply(pbjs.adUnits, adUnitArr); - } else if (typeof adUnitArr === objectType_object) { - pbjs.adUnits.push(adUnitArr); - } +pbjs.addAdUnits = function (adUnitArr) { + if (utils.isArray(adUnitArr)) { + //append array to existing + pbjs.adUnits.push.apply(pbjs.adUnits, adUnitArr); + } else if (typeof adUnitArr === objectType_object) { + pbjs.adUnits.push(adUnitArr); + } }; /** @@ -698,17 +701,18 @@ pbjs.addAdUnits = function(adUnitArr) { * * Currently `bidWon` is the only event that accepts an `id` parameter. */ -pbjs.onEvent = function(event, handler, id) { - if(!utils.isFn(handler)) { - utils.logError('The event handler provided is not a function and was not set on event "' + event + '".'); - return; - } - if(id && !eventValidators[event].call(null, id)){ - utils.logError('The id provided is not valid for event "' + event + '" and no handler was set.'); - return; - } - - events.on(event, handler, id); +pbjs.onEvent = function (event, handler, id) { + if (!utils.isFn(handler)) { + utils.logError('The event handler provided is not a function and was not set on event "' + event + '".'); + return; + } + + if (id && !eventValidators[event].call(null, id)) { + utils.logError('The id provided is not valid for event "' + event + '" and no handler was set.'); + return; + } + + events.on(event, handler, id); }; /** @@ -716,12 +720,12 @@ pbjs.onEvent = function(event, handler, id) { * @param {Function} handler a callback to remove from the event * @param {String} id an identifier in the context of the event (see `pbjs.onEvent`) */ -pbjs.offEvent = function(event, handler, id){ - if(id && !eventValidators[event].call(null, id)){ - return; - } +pbjs.offEvent = function (event, handler, id) { + if (id && !eventValidators[event].call(null, id)) { + return; + } - events.off(event, handler, id); + events.off(event, handler, id); }; /** @@ -731,26 +735,27 @@ pbjs.offEvent = function(event, handler, id){ * @alias module:pbjs.addCallback * @returns {String} id for callback */ -pbjs.addCallback = function(eventStr, func) { - var id = null; - if (!eventStr || !func || typeof func !== objectType_function) { - utils.logError('error registering callback. Check method signature'); - return id; - } - - id = utils.getUniqueIdentifierStr; - bidmanager.addCallback(id, func, eventStr); - return id; +pbjs.addCallback = function (eventStr, func) { + var id = null; + if (!eventStr || !func || typeof func !== objectType_function) { + utils.logError('error registering callback. Check method signature'); + return id; + } + + id = utils.getUniqueIdentifierStr; + bidmanager.addCallback(id, func, eventStr); + return id; }; /** * Remove a callback event - * @param {string} cbId id of the callback to remove + * //@param {string} cbId id of the callback to remove * @alias module:pbjs.removeCallback * @returns {String} id for callback */ -pbjs.removeCallback = function(cbId) { - //todo +pbjs.removeCallback = function (/* cbId */) { + //todo + return null; }; /** @@ -759,44 +764,46 @@ pbjs.removeCallback = function(cbId) { * @param {[type]} bidderCode [description] * @return {[type]} [description] */ -pbjs.registerBidAdapter = function(bidderAdaptor, bidderCode){ - try{ - adaptermanager.registerBidAdapter(bidderAdaptor(), bidderCode); - } - catch(e){ - utils.logError('Error registering bidder adapter : ' + e.message); - } +pbjs.registerBidAdapter = function (bidderAdaptor, bidderCode) { + try { + adaptermanager.registerBidAdapter(bidderAdaptor(), bidderCode); + } + catch (e) { + utils.logError('Error registering bidder adapter : ' + e.message); + } }; /** * */ - pbjs.bidsAvailableForAdapter = function(bidderCode){ - - //TODO getAd - var bids = pb_bidderMap[bidderCode].bids; - - for (var i = 0; i < bids.length; i++) { - var adunitCode = bids[i].placementCode; - var responseObj = bidmanager.pbBidResponseByPlacement[adunitCode]; - - var bid = bidfactory.createBid(1); - // bid.creative_id = adId; - bid.bidderCode = bidderCode; - bid.adUnitCode = adunitCode; - bid.bidder = bidderCode; - // bid.cpm = responseCPM; - // bid.adUrl = jptResponseObj.result.ad; - // bid.width = jptResponseObj.result.width; - // bid.height = jptResponseObj.result.height; - // bid.dealId = jptResponseObj.result.deal_id; - - responseObj.bids.push(bid); - responseObj.bidsReceivedCount++; - bidmanager.pbBidResponseByPlacement[adunitCode] = responseObj; - } +pbjs.bidsAvailableForAdapter = function (bidderCode) { + + //TODO getAd + var bids = pb_bidderMap[bidderCode].bids; + + for (var i = 0; i < bids.length; i++) { + var adunitCode = bids[i].placementCode; + var responseObj = bidmanager.pbBidResponseByPlacement[adunitCode]; + + var bid = bidfactory.createBid(1); + + // bid.creative_id = adId; + bid.bidderCode = bidderCode; + bid.adUnitCode = adunitCode; + bid.bidder = bidderCode; - bidmanager.increaseBidResponseReceivedCount(bidderCode); + // bid.cpm = responseCPM; + // bid.adUrl = jptResponseObj.result.ad; + // bid.width = jptResponseObj.result.width; + // bid.height = jptResponseObj.result.height; + // bid.dealId = jptResponseObj.result.deal_id; + + responseObj.bids.push(bid); + responseObj.bidsReceivedCount++; + bidmanager.pbBidResponseByPlacement[adunitCode] = responseObj; + } + + bidmanager.increaseBidResponseReceivedCount(bidderCode); }; /** @@ -804,8 +811,8 @@ pbjs.registerBidAdapter = function(bidderAdaptor, bidderCode){ * @param {[type]} statusCode [description] * @return {[type]} [description] */ -pbjs.createBid = function(statusCode){ - return bidfactory.createBid(statusCode); +pbjs.createBid = function (statusCode) { + return bidfactory.createBid(statusCode); }; /** @@ -813,8 +820,8 @@ pbjs.createBid = function(statusCode){ * @param {[type]} adUnitCode [description] * @param {[type]} bid [description] */ -pbjs.addBidResponse = function(adUnitCode, bid){ - bidmanager.addBidResponse(adUnitCode, bid); +pbjs.addBidResponse = function (adUnitCode, bid) { + bidmanager.addBidResponse(adUnitCode, bid); }; /** @@ -823,8 +830,8 @@ pbjs.addBidResponse = function(adUnitCode, bid){ * @param {Function} callback [description] * @return {[type]} [description] */ -pbjs.loadScript = function(tagSrc, callback){ - adloader.loadScript(tagSrc, callback); +pbjs.loadScript = function (tagSrc, callback) { + adloader.loadScript(tagSrc, callback); }; /** @@ -833,7 +840,7 @@ pbjs.loadScript = function(tagSrc, callback){ * @param {Function} [description] * @return {[type]} [description] -pbjs.getAnalyticsData = function(){ + pbjs.getAnalyticsData = function(){ var returnObj = {}; var bidResponses = pbjs.getBidResponses(); @@ -890,61 +897,45 @@ pbjs.getAnalyticsData = function(){ return returnObj; }; -*/ + */ /** * Will enable sendinga prebid.js to data provider specified * @param {object} options object {provider : 'string', options : {}} */ -pbjs.enableAnalytics = function(options){ - if(!options){ - utils.logError('pbjs.enableAnalytics should be called with option {}', 'prebid.js'); - return; - } - - if(options.provider === 'ga'){ - try{ - ga.enableAnalytics(typeof options.options === 'undefined' ? {} :options.options ) ; - } - catch(e){ - utils.logError('Error calling GA: ', 'prebid.js', e); - } - } - else if(options.provider === 'other_provider'){ - //todo - } +pbjs.enableAnalytics = function (options) { + if (!options) { + utils.logError('pbjs.enableAnalytics should be called with option {}', 'prebid.js'); + return; + } + + if (options.provider === 'ga') { + try { + ga.enableAnalytics(typeof options.options === 'undefined' ? {} : options.options); + } + catch (e) { + utils.logError('Error calling GA: ', 'prebid.js', e); + } + } else if (options.provider === 'other_provider') { + //todo + return null; + } }; /** * This will tell analytics that all bids received after are "timed out" */ -pbjs.sendTimeoutEvent = function(){ - timeOutBidders(); +pbjs.sendTimeoutEvent = function () { + timeOutBidders(); }; -pbjs.aliasBidder = function(bidderCode,alias){ - - if(bidderCode && alias){ - adaptermanager.aliasBidAdapter(bidderCode,alias); - } - else{ - utils.logError('bidderCode and alias must be passed as arguments', 'pbjs.aliasBidder'); - } +pbjs.aliasBidder = function (bidderCode, alias) { + if (bidderCode && alias) { + adaptermanager.aliasBidAdapter(bidderCode, alias); + } else { + utils.logError('bidderCode and alias must be passed as arguments', 'pbjs.aliasBidder'); + } }; - processQue(); - -// @ifdef DEBUG -//only for test -pbjs_testonly = {}; - -pbjs_testonly.getAdUnits = function() { - return pbjs.adUnits; -}; - -pbjs_testonly.clearAllAdUnits = function(){ - pbjs.adUnits =[]; -}; -// @endif diff --git a/src/utils.js b/src/utils.js index 67feca7ee7e..11e27489f73 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,8 +1,6 @@ var CONSTANTS = require('./constants.json'); var polyfills = require('./polyfills'); -var objectType_function = 'function'; -var objectType_undefined = 'undefined'; var objectType_object = 'object'; var objectType_string = 'string'; var objectType_number = 'number'; @@ -13,15 +11,13 @@ var _lgPriceCap = 5.00; var _mgPriceCap = 20.00; var _hgPriceCap = 20.00; -var t_Arr = 'Array', - t_Str = 'String', - t_Fn = 'Function', - toString = Object.prototype.toString, - hasOwnProperty = Object.prototype.hasOwnProperty, - slice = Array.prototype.slice; +var t_Arr = 'Array'; +var t_Str = 'String'; +var t_Fn = 'Function'; +var toString = Object.prototype.toString; /* - * Substitues into a string from a given map using the token + * Substitutes into a string from a given map using the token * Usage * var str = 'text %%REPLACE%% this text with %%SOMETHING%%'; * var map = {}; @@ -29,157 +25,163 @@ var t_Arr = 'Array', * map['something'] = 'something else'; * console.log(replaceTokenInString(str, map, '%%')); => "text it was subbed this text with something else" */ -exports.replaceTokenInString = function(str, map, token) { +exports.replaceTokenInString = function (str, map, token) { this._each(map, function (value, key) { value = (value === undefined) ? '' : value; - var keyString = token + key.toUpperCase() + token, - re = new RegExp(keyString, 'g'); + var keyString = token + key.toUpperCase() + token; + var re = new RegExp(keyString, 'g'); str = str.replace(re, value); }); + return str; }; /* utility method to get incremental integer starting from 1 */ -var getIncrementalInteger = (function() { - var count = 0; - return function() { - count++; - return count; - }; +var getIncrementalInteger = (function () { + var count = 0; + return function () { + count++; + return count; + }; })(); function _getUniqueIdentifierStr() { - return getIncrementalInteger() + Math.random().toString(16).substr(2); + return getIncrementalInteger() + Math.random().toString(16).substr(2); } //generate a random string (to be used as a dynamic JSONP callback) exports.getUniqueIdentifierStr = _getUniqueIdentifierStr; -exports.getBidIdParamater = function(key, paramsObj) { - if (paramsObj && paramsObj[key]) { - return paramsObj[key]; - } - return ''; -}; +exports.getBidIdParamater = function (key, paramsObj) { + if (paramsObj && paramsObj[key]) { + return paramsObj[key]; + } -exports.tryAppendQueryString = function(existingUrl, key, value) { - if (value) { - return existingUrl += key + '=' + encodeURIComponent(value) + '&'; - } - return existingUrl; + return ''; }; +exports.tryAppendQueryString = function (existingUrl, key, value) { + if (value) { + return existingUrl += key + '=' + encodeURIComponent(value) + '&'; + } + + return existingUrl; +}; //parse a query string object passed in bid params //bid params should be an object such as {key: "value", key1 : "value1"} -exports.parseQueryStringParameters = function(queryObj) { - var result = ""; - for (var k in queryObj){ - if (queryObj.hasOwnProperty(k)) - result += k + "=" + encodeURIComponent(queryObj[k]) + "&"; - } - return result; +exports.parseQueryStringParameters = function (queryObj) { + var result = ''; + for (var k in queryObj) { + if (queryObj.hasOwnProperty(k)) + result += k + '=' + encodeURIComponent(queryObj[k]) + '&'; + } + + return result; }; - //transform an AdServer targeting bids into a query string to send to the adserver //bid params should be an object such as {key: "value", key1 : "value1"} -exports.transformAdServerTargetingObj = function(adServerTargeting) { - var result = ""; - if (!adServerTargeting) - return ""; - for (var k in adServerTargeting) - if (adServerTargeting.hasOwnProperty(k)) - result += k + "=" + encodeURIComponent(adServerTargeting[k]) + "&"; - return result; +exports.transformAdServerTargetingObj = function (adServerTargeting) { + var result = ''; + if (!adServerTargeting) + return ''; + for (var k in adServerTargeting) + if (adServerTargeting.hasOwnProperty(k)) + result += k + '=' + encodeURIComponent(adServerTargeting[k]) + '&'; + return result; }; //Copy all of the properties in the source objects over to the target object //return the target object. -exports.extend = function(target, source){ - target = target || {}; - - this._each(source,function(value,prop){ - if (typeof source[prop] === objectType_object) { - target[prop] = this.extend(target[prop], source[prop]); - } else { - target[prop] = source[prop]; - } - }); - return target; +exports.extend = function (target, source) { + target = target || {}; + + this._each(source, function (value, prop) { + if (typeof source[prop] === objectType_object) { + target[prop] = this.extend(target[prop], source[prop]); + } else { + target[prop] = source[prop]; + } + }); + + return target; }; /** * Parse a GPT-Style general size Array like `[[300, 250]]` or `"300x250,970x90"` into an array of sizes `["300x250"]` or '['300x250', '970x90']' * @param {array[array|number]} sizeObj Input array or double array [300,250] or [[300,250], [728,90]] - * @return {array[string]} Array of strings like `["300x250"]` or `["300x250", "728x90"]` + * @return {array[string]} Array of strings like `["300x250"]` or `["300x250", "728x90"]` */ -exports.parseSizesInput = function(sizeObj) { - var parsedSizes = []; - - //if a string for now we can assume it is a single size, like "300x250" - if (typeof sizeObj === objectType_string) { - //multiple sizes will be comma-separated - var sizes = sizeObj.split(','); - //regular expression to match strigns like 300x250 - //start of line, at least 1 number, an "x" , then at least 1 number, and the then end of the line - var sizeRegex = /^(\d)+x(\d)+$/i; - if (sizes) { - for (var curSizePos in sizes) { - if (hasOwn(sizes, curSizePos) && sizes[curSizePos].match(sizeRegex)) { - parsedSizes.push(sizes[curSizePos]); - } - } - } - } else if (typeof sizeObj === objectType_object) { - var sizeArrayLength = sizeObj.length; - //don't process empty array - if (sizeArrayLength > 0) { - //if we are a 2 item array of 2 numbers, we must be a SingleSize array - if (sizeArrayLength === 2 && typeof sizeObj[0] === objectType_number && typeof sizeObj[1] === objectType_number) { - parsedSizes.push(this.parseGPTSingleSizeArray(sizeObj)); - } else { - //otherwise, we must be a MultiSize array - for (var i = 0; i < sizeArrayLength; i++) { - parsedSizes.push(this.parseGPTSingleSizeArray(sizeObj[i])); - } - - } - } - } - - return parsedSizes; +exports.parseSizesInput = function (sizeObj) { + var parsedSizes = []; + + //if a string for now we can assume it is a single size, like "300x250" + if (typeof sizeObj === objectType_string) { + //multiple sizes will be comma-separated + var sizes = sizeObj.split(','); + + //regular expression to match strigns like 300x250 + //start of line, at least 1 number, an "x" , then at least 1 number, and the then end of the line + var sizeRegex = /^(\d)+x(\d)+$/i; + if (sizes) { + for (var curSizePos in sizes) { + if (hasOwn(sizes, curSizePos) && sizes[curSizePos].match(sizeRegex)) { + parsedSizes.push(sizes[curSizePos]); + } + } + } + } else if (typeof sizeObj === objectType_object) { + var sizeArrayLength = sizeObj.length; + + //don't process empty array + if (sizeArrayLength > 0) { + //if we are a 2 item array of 2 numbers, we must be a SingleSize array + if (sizeArrayLength === 2 && typeof sizeObj[0] === objectType_number && typeof sizeObj[1] === objectType_number) { + parsedSizes.push(this.parseGPTSingleSizeArray(sizeObj)); + } else { + //otherwise, we must be a MultiSize array + for (var i = 0; i < sizeArrayLength; i++) { + parsedSizes.push(this.parseGPTSingleSizeArray(sizeObj[i])); + } + + } + } + } + + return parsedSizes; }; //parse a GPT style sigle size array, (i.e [300,250]) //into an AppNexus style string, (i.e. 300x250) -exports.parseGPTSingleSizeArray = function(singleSize) { - //if we aren't exactly 2 items in this array, it is invalid +exports.parseGPTSingleSizeArray = function (singleSize) { + //if we aren't exactly 2 items in this array, it is invalid if (this.isArray(singleSize) && singleSize.length === 2 && (!isNaN(singleSize[0]) && !isNaN(singleSize[1]))) { - return singleSize[0] + 'x' + singleSize[1]; - } + return singleSize[0] + 'x' + singleSize[1]; + } }; -exports.getTopWindowUrl = function() { - try { - return window.top.location.href; - } catch (e) { - return window.location.href; - } +exports.getTopWindowUrl = function () { + try { + return window.top.location.href; + } catch (e) { + return window.location.href; + } }; -exports.logMessage = function(msg) { - if (debugTurnedOn() && hasConsoleLogger()) { - console.log('MESSAGE: ' + msg); - } +exports.logMessage = function (msg) { + if (debugTurnedOn() && hasConsoleLogger()) { + console.log('MESSAGE: ' + msg); + } }; function hasConsoleLogger() { - return (window.console && window.console.log); + return (window.console && window.console.log); } + exports.hasConsoleLogger = hasConsoleLogger; var errLogFn = (function (hasLogger) { @@ -187,97 +189,96 @@ var errLogFn = (function (hasLogger) { return window.console.error ? 'error' : 'log'; }(hasConsoleLogger())); -var debugTurnedOn = function() { - if (pbjs.logging === false && _loggingChecked === false) { - pbjs.logging = getParameterByName(CONSTANTS.DEBUG_MODE).toUpperCase() === 'TRUE'; - _loggingChecked = true; - } - - if (pbjs.logging) { - return true; - } - return false; +var debugTurnedOn = function () { + if (pbjs.logging === false && _loggingChecked === false) { + pbjs.logging = getParameterByName(CONSTANTS.DEBUG_MODE).toUpperCase() === 'TRUE'; + _loggingChecked = true; + } + return !!pbjs.logging; }; + exports.debugTurnedOn = debugTurnedOn; -exports.logError = function(msg, code, exception) { - var errCode = code || 'ERROR'; - if (debugTurnedOn() && hasConsoleLogger()) { +exports.logError = function (msg, code, exception) { + var errCode = code || 'ERROR'; + if (debugTurnedOn() && hasConsoleLogger()) { console[errLogFn].call(console, errCode + ': ' + msg, exception || ''); - } + } }; exports.createInvisibleIframe = function _createInvisibleIframe() { - var f = document.createElement('iframe'); - f.id = _getUniqueIdentifierStr(); - f.height = 0; - f.width = 0; - f.border = '0px'; - f.hspace = '0'; - f.vspace = '0'; - f.marginWidth = '0'; - f.marginHeight = '0'; - f.style.border = '0'; - f.scrolling = 'no'; - f.frameBorder = '0'; - f.src = 'about:self'; - f.style = 'display:none'; - return f; + var f = document.createElement('iframe'); + f.id = _getUniqueIdentifierStr(); + f.height = 0; + f.width = 0; + f.border = '0px'; + f.hspace = '0'; + f.vspace = '0'; + f.marginWidth = '0'; + f.marginHeight = '0'; + f.style.border = '0'; + f.scrolling = 'no'; + f.frameBorder = '0'; + f.src = 'about:self'; + f.style = 'display:none'; + return f; }; /* - * Check if a given paramater name exists in query string + * Check if a given parameter name exists in query string * and if it does return the value */ -var getParameterByName = function(name) { - var regexS = '[\\?&]' + name + '=([^&#]*)', - regex = new RegExp(regexS), - results = regex.exec(window.location.search); - if (results === null) { - return ''; - } - return decodeURIComponent(results[1].replace(/\+/g, ' ')); +var getParameterByName = function (name) { + var regexS = '[\\?&]' + name + '=([^&#]*)'; + var regex = new RegExp(regexS); + var results = regex.exec(window.location.search); + if (results === null) { + return ''; + } + + return decodeURIComponent(results[1].replace(/\+/g, ' ')); }; -exports.getPriceBucketString = function(cpm) { - var low = '', - med = '', - high = '', - cpmFloat = 0, - returnObj = { - low: low, - med: med, - high: high - }; - try { - cpmFloat = parseFloat(cpm); - if (cpmFloat) { - //round to closet .5 - if (cpmFloat > _lgPriceCap) { - returnObj.low = _lgPriceCap.toFixed(2); - } else { - returnObj.low = (Math.floor(cpm * 2) / 2).toFixed(2); - } - - //round to closet .1 - if (cpmFloat > _mgPriceCap) { - returnObj.med = _mgPriceCap.toFixed(2); - } else { - returnObj.med = (Math.floor(cpm * 10) / 10).toFixed(2); - } - - //round to closet .01 - if (cpmFloat > _hgPriceCap) { - returnObj.high = _hgPriceCap.toFixed(2); - } else { - returnObj.high = (Math.floor(cpm * 100) / 100).toFixed(2); - } - } - } catch (e) { - this.logError('Exception parsing CPM :' + e.message); - } - return returnObj; +exports.getPriceBucketString = function (cpm) { + var low = ''; + var med = ''; + var high = ''; + var cpmFloat = 0; + var returnObj = { + low: low, + med: med, + high: high + }; + try { + cpmFloat = parseFloat(cpm); + if (cpmFloat) { + //round to closet .5 + if (cpmFloat > _lgPriceCap) { + returnObj.low = _lgPriceCap.toFixed(2); + } else { + returnObj.low = (Math.floor(cpm * 2) / 2).toFixed(2); + } + + //round to closet .1 + if (cpmFloat > _mgPriceCap) { + returnObj.med = _mgPriceCap.toFixed(2); + } else { + returnObj.med = (Math.floor(cpm * 10) / 10).toFixed(2); + } + + //round to closet .01 + if (cpmFloat > _hgPriceCap) { + returnObj.high = _hgPriceCap.toFixed(2); + } else { + returnObj.high = (Math.floor(cpm * 100) / 100).toFixed(2); + } + } + } catch (e) { + this.logError('Exception parsing CPM :' + e.message); + } + + return returnObj; }; @@ -287,43 +288,46 @@ exports.getPriceBucketString = function(cpm) { * @param {string[]} requiredParamsArr [description] * @return {bool} Bool if paramaters are valid */ -exports.hasValidBidRequest = function(paramObj, requiredParamsArr, adapter){ +exports.hasValidBidRequest = function (paramObj, requiredParamsArr, adapter) { + var found = false; - for(var i = 0; i < requiredParamsArr.length; i++){ - var found = false; + function findParam(value, key) { + if (key === requiredParamsArr[i]) { + found = true; + } + } - this._each(paramObj, function (value, key) { - if (key === requiredParamsArr[i]) { - found = true; - } - }); + for (var i = 0; i < requiredParamsArr.length; i++) { + found = false; - if(!found){ - this.logError('Params are missing for bid request. One of these required paramaters are missing: ' + requiredParamsArr, adapter); - return false; - } - } + this._each(paramObj, findParam); + + if (!found) { + this.logError('Params are missing for bid request. One of these required paramaters are missing: ' + requiredParamsArr, adapter); + return false; + } + } - return true; + return true; }; // Handle addEventListener gracefully in older browsers -exports.addEventHandler = function(element, event, func) { - if (element.addEventListener) { - element.addEventListener(event, func, true); - } else if (element.attachEvent) { - element.attachEvent('on' + event, func); - } - }; - /** - * Return if the object is of the - * given type. - * @param {*} object to test - * @param {String} _t type string (e.g., Array) - * @return {Boolean} if object is of type _t - */ -exports.isA = function(object, _t) { - return toString.call(object) === '[object ' + _t + ']'; +exports.addEventHandler = function (element, event, func) { + if (element.addEventListener) { + element.addEventListener(event, func, true); + } else if (element.attachEvent) { + element.attachEvent('on' + event, func); + } +}; +/** + * Return if the object is of the + * given type. + * @param {*} object to test + * @param {String} _t type string (e.g., Array) + * @return {Boolean} if object is of type _t + */ +exports.isA = function (object, _t) { + return toString.call(object) === '[object ' + _t + ']'; }; exports.isFn = function (object) { @@ -344,58 +348,66 @@ exports.isArray = function (object) { * @param {*} object object to test * @return {Boolean} if object is empty */ -exports.isEmpty = function(object) { - if (!object) return true; - if (this.isArray(object) || this.isStr(object)) return !(object.length > 0); - for (var k in object) { - if (hasOwnProperty.call(object, k)) return false; - } - return true; - }; +exports.isEmpty = function (object) { + if (!object) return true; + if (this.isArray(object) || this.isStr(object)) { + return !(object.length > 0); // jshint ignore:line + } - /** - * Iterate object with the function - * falls back to es5 `forEach` - * @param {Array|Object} object - * @param {Function(value, key, object)} fn - */ -exports._each = function(object, fn) { - if (this.isEmpty(object)) return; - if (this.isFn(object.forEach)) return object.forEach(fn, this); - - var k = 0, - l = object.length; - - if (l > 0) { - for (; k < l; k++) fn(object[k], k, object); - } else { - for (k in object) { - if (hasOwnProperty.call(object, k)) fn.call(this, object[k], k); - } - } - }; + for (var k in object) { + if (hasOwnProperty.call(object, k)) return false; + } + + return true; +}; -exports.contains = function(a, obj) { - if(this.isEmpty(a)){ - return false; - } - if (this.isFn(a.indexOf)) { - return a.indexOf(obj) !== -1; - } - var i = a.length; - while (i--) { - if (a[i] === obj) { - return true; - } +/** + * Iterate object with the function + * falls back to es5 `forEach` + * @param {Array|Object} object + * @param {Function(value, key, object)} fn + */ +exports._each = function (object, fn) { + if (this.isEmpty(object)) return; + if (this.isFn(object.forEach)) return object.forEach(fn, this); + + var k = 0; + var l = object.length; + + if (l > 0) { + for (; k < l; k++) fn(object[k], k, object); + } else { + for (k in object) { + if (hasOwnProperty.call(object, k)) fn.call(this, object[k], k); } + } +}; + +exports.contains = function (a, obj) { + if (this.isEmpty(a)) { return false; + } + + if (this.isFn(a.indexOf)) { + return a.indexOf(obj) !== -1; + } + + var i = a.length; + while (i--) { + if (a[i] === obj) { + return true; + } + } + + return false; }; -exports.indexOf = (function() { - if(Array.prototype.indexOf) { - return Array.prototype.indexOf; - } - return polyfills.indexOf; +exports.indexOf = (function () { + if (Array.prototype.indexOf) { + return Array.prototype.indexOf; + } + + return polyfills.indexOf; }()); /** @@ -412,13 +424,14 @@ exports._map = function (object, callback) { this._each(object, function (value, key) { output.push(callback(value, key, object)); }); + return output; }; -var hasOwn = function(objectToCheck, propertyToCheckFor) { - if (objectToCheck.hasOwnProperty) { - return objectToCheck.hasOwnProperty(propertyToCheckFor); - } else { - return (typeof objectToCheck[propertyToCheckFor] !== UNDEFINED) && (objectToCheck.constructor.prototype[propertyToCheckFor] !== objectToCheck[propertyToCheckFor]); - } +var hasOwn = function (objectToCheck, propertyToCheckFor) { + if (objectToCheck.hasOwnProperty) { + return objectToCheck.hasOwnProperty(propertyToCheckFor); + } else { + return (typeof objectToCheck[propertyToCheckFor] !== 'undefined') && (objectToCheck.constructor.prototype[propertyToCheckFor] !== objectToCheck[propertyToCheckFor]); + } }; diff --git a/styleRules.jscs.json b/styleRules.jscs.json deleted file mode 100644 index d39e51b1a24..00000000000 --- a/styleRules.jscs.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "excludeFiles": [""], - "validateQuoteMarks": "'", - "disallowNewlineBeforeBlockStatements": true -} diff --git a/test/helpers/pbjs-test-only.js b/test/helpers/pbjs-test-only.js new file mode 100644 index 00000000000..c2c1e9ef406 --- /dev/null +++ b/test/helpers/pbjs-test-only.js @@ -0,0 +1,10 @@ +export const pbjsTestOnly = { + + getAdUnits() { + return pbjs.adUnits; + }, + + clearAllAdUnits() { + pbjs.adUnits = []; + } +}; diff --git a/test/spec/adUnits_spec.js b/test/spec/adUnits_spec.js index 9fbc29b6860..5bf39584493 100644 --- a/test/spec/adUnits_spec.js +++ b/test/spec/adUnits_spec.js @@ -1,131 +1,125 @@ -describe("Publisher API _ AdUnits", function() { - var assert = require('chai').assert, - expect = require('chai').expect, - should = require('chai').should(); - - var prebid = require('../../src/prebid'); - - - before(function(){ - var adUnits = [{ - code: "/1996833/slot-1", - sizes: [[300, 250], [728, 90]], - bids: [ +describe('Publisher API _ AdUnits', function () { + var assert = require('chai').assert; + var expect = require('chai').expect; + var pbjsTestOnly = require('../helpers/pbjs-test-only').pbjsTestOnly; + + before(function () { + var adUnits = [{ + code: '/1996833/slot-1', + sizes: [[300, 250], [728, 90]], + bids: [ { - bidder: "openx", - params: { - pgid: "2342353", - unit: "234234", - jstag_url: "http://" - } - },{ - bidder: "appnexus", - params: { - placementId: "234235" - } + bidder: 'openx', + params: { + pgid: '2342353', + unit: '234234', + jstag_url: 'http://' + } + }, { + bidder: 'appnexus', + params: { + placementId: '234235' + } } ] - },{ - code: "/1996833/slot-2", - sizes: [[468, 60]], - bids: [ + }, { + code: '/1996833/slot-2', + sizes: [[468, 60]], + bids: [ { - bidder: "rubicon", - params: { - rp_account: "4934", - rp_site: "13945", - rp_zonesize: "23948-15" - } - },{ - bidder: "appnexus", - params: { - placementId: "827326" - } + bidder: 'rubicon', + params: { + rp_account: '4934', + rp_site: '13945', + rp_zonesize: '23948-15' + } + }, { + bidder: 'appnexus', + params: { + placementId: '827326' + } } ] - }]; + }]; + + pbjs.addAdUnits(adUnits); + }); + + after(function () { + pbjsTestOnly.clearAllAdUnits(); + }); + + describe('addAdUnits', function () { + + var adUnits, adUnit1, bids1, adUnit2, bids2; - pbjs.addAdUnits(adUnits); + it('should have two adUnits', function () { + adUnits = pbjsTestOnly.getAdUnits(); + adUnit1 = adUnits[0]; + bids1 = adUnit1.bids; + adUnit2 = adUnits[1]; + bids2 = adUnit2.bids; }); - after(function(){ - pbjs_testonly.clearAllAdUnits(); + it('the first adUnits value should be same with the adUnits that is added by pbjs.addAdUnits();', function () { + assert.strictEqual(adUnit1.code, '/1996833/slot-1', 'adUnit1 code'); + assert.deepEqual(adUnit1.sizes, [[300, 250], [728, 90]], 'adUnit1 sizes'); + assert.strictEqual(bids1[0].bidder, 'openx', 'adUnit1 bids1 bidder'); + assert.strictEqual(bids1[0].params.pgid, '2342353', 'adUnit1 bids1 params.pgid'); + assert.strictEqual(bids1[0].params.unit, '234234', 'adUnit1 bids1 params.unit'); + assert.strictEqual(bids1[0].params.jstag_url, 'http://', 'adUnit1 bids1 params.jstag_url'); + + assert.strictEqual(bids1[1].bidder, 'appnexus', 'adUnit1 bids2 bidder'); + assert.strictEqual(bids1[1].params.placementId, '234235', 'adUnit1 bids2 params.placementId'); + + assert.strictEqual(adUnit2.code, '/1996833/slot-2', 'adUnit2 code'); + assert.deepEqual(adUnit2.sizes, [[468, 60]], 'adUnit2 sizes'); + assert.strictEqual(bids2[0].bidder, 'rubicon', 'adUnit2 bids1 bidder'); + assert.strictEqual(bids2[0].params.rp_account, '4934', 'adUnit2 bids1 params.rp_account'); + assert.strictEqual(bids2[0].params.rp_zonesize, '23948-15', 'adUnit2 bids1 params.rp_zonesize'); + assert.strictEqual(bids2[0].params.rp_site, '13945', 'adUnit2 bids1 params.rp_site'); + + assert.strictEqual(bids2[1].bidder, 'appnexus', 'adUnit2 bids2 bidder'); + assert.strictEqual(bids2[1].params.placementId, '827326', 'adUnit2 bids2 params.placementId'); }); - - - describe('addAdUnits', function() { - - var adUnits,adUnit1,bids1,adUnit2,bids2; - - it('should have two adUnits',function(){ - adUnits = pbjs_testonly.getAdUnits(); - adUnit1 = adUnits[0]; - bids1 = adUnit1.bids; - adUnit2 = adUnits[1]; - bids2 = adUnit2.bids; - }); - - it('the first adUnits value should be same with the adUnits that is added by pbjs.addAdUnits();',function(){ - assert.strictEqual(adUnit1.code,'/1996833/slot-1','adUnit1 code'); - assert.deepEqual(adUnit1.sizes,[[300, 250], [728, 90]],'adUnit1 sizes'); - assert.strictEqual(bids1[0].bidder,'openx','adUnit1 bids1 bidder'); - assert.strictEqual(bids1[0].params.pgid,'2342353','adUnit1 bids1 params.pgid'); - assert.strictEqual(bids1[0].params.unit,'234234','adUnit1 bids1 params.unit'); - assert.strictEqual(bids1[0].params.jstag_url,'http://','adUnit1 bids1 params.jstag_url'); - - assert.strictEqual(bids1[1].bidder,'appnexus','adUnit1 bids2 bidder'); - assert.strictEqual(bids1[1].params.placementId,'234235','adUnit1 bids2 params.placementId'); - - assert.strictEqual(adUnit2.code,'/1996833/slot-2','adUnit2 code'); - assert.deepEqual(adUnit2.sizes,[[468, 60]],'adUnit2 sizes'); - assert.strictEqual(bids2[0].bidder,'rubicon','adUnit2 bids1 bidder'); - assert.strictEqual(bids2[0].params.rp_account,'4934','adUnit2 bids1 params.rp_account'); - assert.strictEqual(bids2[0].params.rp_zonesize,'23948-15','adUnit2 bids1 params.rp_zonesize'); - assert.strictEqual(bids2[0].params.rp_site,'13945','adUnit2 bids1 params.rp_site'); - - assert.strictEqual(bids2[1].bidder,'appnexus','adUnit2 bids2 bidder'); - assert.strictEqual(bids2[1].params.placementId,'827326','adUnit2 bids2 params.placementId'); - }); - - it('the second adUnits value should be same with the adUnits that is added by pbjs.addAdUnits();',function(){ - - assert.strictEqual(adUnit2.code,'/1996833/slot-2','adUnit2 code'); - assert.deepEqual(adUnit2.sizes,[[468, 60]],'adUnit2 sizes'); - assert.strictEqual(bids2[0].bidder,'rubicon','adUnit2 bids1 bidder'); - assert.strictEqual(bids2[0].params.rp_account,'4934','adUnit2 bids1 params.rp_account'); - assert.strictEqual(bids2[0].params.rp_zonesize,'23948-15','adUnit2 bids1 params.rp_zonesize'); - assert.strictEqual(bids2[0].params.rp_site,'13945','adUnit2 bids1 params.rp_site'); - - assert.strictEqual(bids2[1].bidder,'appnexus','adUnit2 bids2 bidder'); - assert.strictEqual(bids2[1].params.placementId,'827326','adUnit2 bids2 params.placementId'); - }); + + it('the second adUnits value should be same with the adUnits that is added by pbjs.addAdUnits();', function () { + + assert.strictEqual(adUnit2.code, '/1996833/slot-2', 'adUnit2 code'); + assert.deepEqual(adUnit2.sizes, [[468, 60]], 'adUnit2 sizes'); + assert.strictEqual(bids2[0].bidder, 'rubicon', 'adUnit2 bids1 bidder'); + assert.strictEqual(bids2[0].params.rp_account, '4934', 'adUnit2 bids1 params.rp_account'); + assert.strictEqual(bids2[0].params.rp_zonesize, '23948-15', 'adUnit2 bids1 params.rp_zonesize'); + assert.strictEqual(bids2[0].params.rp_site, '13945', 'adUnit2 bids1 params.rp_site'); + + assert.strictEqual(bids2[1].bidder, 'appnexus', 'adUnit2 bids2 bidder'); + assert.strictEqual(bids2[1].params.placementId, '827326', 'adUnit2 bids2 params.placementId'); + }); + }); + + describe('removeAdUnit', function () { + + var adUnits, adUnit2, bids2; + + it('the first adUnit should be not existed', function () { + pbjs.removeAdUnit('/1996833/slot-1'); + adUnits = pbjsTestOnly.getAdUnits(); + adUnit2 = adUnits[0]; + bids2 = adUnit2.bids; + expect(adUnits[1]).not.exist; }); - describe('removeAdUnit',function(){ - - var adUnits,adUnit2,bids2; - - - it('the first adUnit should be not existed',function(){ - pbjs.removeAdUnit('/1996833/slot-1'); - adUnits = pbjs_testonly.getAdUnits(); - adUnit2 = adUnits[0]; - bids2 = adUnit2.bids; - expect(adUnits[1]).not.exist; - }); - - it('the second adUnit should be still existed',function(){ - assert.strictEqual(adUnit2.code,'/1996833/slot-2','adUnit2 code'); - assert.deepEqual(adUnit2.sizes,[[468, 60]],'adUnit2 sizes'); - assert.strictEqual(bids2[0].bidder,'rubicon','adUnit2 bids1 bidder'); - assert.strictEqual(bids2[0].params.rp_account,'4934','adUnit2 bids1 params.rp_account'); - assert.strictEqual(bids2[0].params.rp_zonesize,'23948-15','adUnit2 bids1 params.rp_zonesize'); - assert.strictEqual(bids2[0].params.rp_site,'13945','adUnit2 bids1 params.rp_site'); - - assert.strictEqual(bids2[1].bidder,'appnexus','adUnit2 bids2 bidder'); - assert.strictEqual(bids2[1].params.placementId,'827326','adUnit2 bids2 params.placementId'); - }); + it('the second adUnit should be still existed', function () { + assert.strictEqual(adUnit2.code, '/1996833/slot-2', 'adUnit2 code'); + assert.deepEqual(adUnit2.sizes, [[468, 60]], 'adUnit2 sizes'); + assert.strictEqual(bids2[0].bidder, 'rubicon', 'adUnit2 bids1 bidder'); + assert.strictEqual(bids2[0].params.rp_account, '4934', 'adUnit2 bids1 params.rp_account'); + assert.strictEqual(bids2[0].params.rp_zonesize, '23948-15', 'adUnit2 bids1 params.rp_zonesize'); + assert.strictEqual(bids2[0].params.rp_site, '13945', 'adUnit2 bids1 params.rp_site'); + + assert.strictEqual(bids2[1].bidder, 'appnexus', 'adUnit2 bids2 bidder'); + assert.strictEqual(bids2[1].params.placementId, '827326', 'adUnit2 bids2 params.placementId'); }); + }); - }); diff --git a/test/spec/adloader_spec.js b/test/spec/adloader_spec.js index 0ff86b35dc0..49513251f60 100644 --- a/test/spec/adloader_spec.js +++ b/test/spec/adloader_spec.js @@ -1,10 +1,10 @@ -describe('adLoader', function() { +describe('adLoader', function () { var assert = require('chai').assert, adLoader = require('../../src/adloader'); - describe('trackPixel', function() { - it('correctly appends a cachebuster query paramter to a pixel with no existing parameters', function() { - var inputUrl = "http://www.example.com/tracking_pixel", + describe('trackPixel', function () { + it('correctly appends a cachebuster query paramter to a pixel with no existing parameters', function () { + var inputUrl = 'http://www.example.com/tracking_pixel', token = '?rnd=', expectedPartialUrl = inputUrl + token, actual = adLoader.trackPixel(inputUrl), @@ -15,8 +15,8 @@ describe('adLoader', function() { }); }); - it('correctly appends a cachebuster query paramter to a pixel with one existing parameter', function() { - var inputUrl = "http://www.example.com/tracking_pixel?food=bard", + it('correctly appends a cachebuster query paramter to a pixel with one existing parameter', function () { + var inputUrl = 'http://www.example.com/tracking_pixel?food=bard', token = '&rnd=', expectedPartialUrl = inputUrl + token, actual = adLoader.trackPixel(inputUrl), @@ -26,8 +26,8 @@ describe('adLoader', function() { assert.isNumber(randomNumber); }); - it('correctly appends a cachebuster query paramter to a pixel with multiple existing parameters', function() { - var inputUrl = "http://www.example.com/tracking_pixel?food=bard&zing=zang", + it('correctly appends a cachebuster query paramter to a pixel with multiple existing parameters', function () { + var inputUrl = 'http://www.example.com/tracking_pixel?food=bard&zing=zang', token = '&rnd=', expectedPartialUrl = inputUrl + token, actual = adLoader.trackPixel(inputUrl), diff --git a/test/spec/aliasBidder_spec.js b/test/spec/aliasBidder_spec.js index c2410399bfc..9a537dcf86d 100644 --- a/test/spec/aliasBidder_spec.js +++ b/test/spec/aliasBidder_spec.js @@ -1,39 +1,42 @@ -describe("Publisher API _ Alias Bidder", function() { - var assert = require('chai').assert, - expect = require('chai').expect, - should = require('chai').should(); - var prebid = require('../../src/prebid'); - - before(function(){ - - var topSlotCode = '/19968336/header-bid-tag1'; - var topSlotSizes = [[728, 90], [970, 90]]; - var adUnit = { - code: topSlotCode, - sizes: topSlotSizes, - bids: [{ - bidder: 'appnexus', - params: { - placementId : '5215561' - } - }] - }; - - pbjs.addAdUnits(adUnit); - }); +import { pbjsTestOnly } from 'test/helpers/pbjs-test-only'; - after(function(){ - pbjs_testonly.clearAllAdUnits(); - }); - - describe('set Alias Bidder', function () { +describe('Publisher API _ Alias Bidder', function () { + var assert = require('chai').assert; + var expect = require('chai').expect; + var should = require('chai').should(); + var prebid = require('../../src/prebid'); + + before(function () { + + var topSlotCode = '/19968336/header-bid-tag1'; + var topSlotSizes = [[728, 90], [970, 90]]; + var adUnit = { + code: topSlotCode, + sizes: topSlotSizes, + bids: [ + { + bidder: 'appnexus', + params: { + placementId: '5215561' + } + } + ] + }; + + pbjs.addAdUnits(adUnit); + }); + + after(function () { + pbjsTestOnly.clearAllAdUnits(); + }); + + describe('set Alias Bidder', function () { + + it('should have both of target bidder and alias bidder', function () { - it('should have both of target bidder and alias bidder', function() { - - pbjs.aliasBidder('appnexus','bRealTime1'); + pbjs.aliasBidder('appnexus', 'bRealTime1'); - }); }); + }); - }); diff --git a/test/spec/api_spec.js b/test/spec/api_spec.js index fd6f296e746..c0be859958e 100755 --- a/test/spec/api_spec.js +++ b/test/spec/api_spec.js @@ -1,65 +1,76 @@ var assert = require('chai').assert; var prebid = require('../../src/prebid'); - -describe("Publisher API", function() { +describe('Publisher API', function () { // var assert = chai.assert; - describe('api of command queue',function(){ + describe('api of command queue', function () { - it('should have a global variable pbjs', function() { + it('should have a global variable pbjs', function () { assert.isObject(pbjs); }); - it('should have a global variable pbjs.que as an array',function(){ + it('should have a global variable pbjs.que as an array', function () { assert.isArray(pbjs.que); }); - it('should have pbjs.que.push function', function(){ + it('should have pbjs.que.push function', function () { assert.isFunction(pbjs.que.push); }); }); - describe('has function',function(){ + describe('has function', function () { - it('should have function pbjs.getAdserverTargeting',function(){ + it('should have function pbjs.getAdserverTargeting', function () { assert.isFunction(pbjs.getAdserverTargeting); }); - it('should have function pbjs.getAdserverTargetingForAdUnitCode',function(){ - assert.isFunction(pbjs.getAdserverTargetingForAdUnitCode); + + it('should have function pbjs.getAdserverTargetingForAdUnitCode', function () { + assert.isFunction(pbjs.getAdserverTargetingForAdUnitCode); }); - it('should have function pbjs.getBidResponses',function(){ - assert.isFunction(pbjs.getBidResponses); + + it('should have function pbjs.getBidResponses', function () { + assert.isFunction(pbjs.getBidResponses); }); - it('should have function pbjs.getBidResponsesForAdUnitCode',function(){ - assert.isFunction(pbjs.getBidResponsesForAdUnitCode); + + it('should have function pbjs.getBidResponsesForAdUnitCode', function () { + assert.isFunction(pbjs.getBidResponsesForAdUnitCode); }); - it('should have function pbjs.setTargetingForGPTAsync',function(){ - assert.isFunction(pbjs.setTargetingForGPTAsync); + + it('should have function pbjs.setTargetingForGPTAsync', function () { + assert.isFunction(pbjs.setTargetingForGPTAsync); }); - it('should have function pbjs.allBidsAvailable',function(){ - assert.isFunction(pbjs.allBidsAvailable); + + it('should have function pbjs.allBidsAvailable', function () { + assert.isFunction(pbjs.allBidsAvailable); }); - it('should have function pbjs.renderAd',function(){ - assert.isFunction(pbjs.renderAd); + + it('should have function pbjs.renderAd', function () { + assert.isFunction(pbjs.renderAd); }); - it('should have function pbjs.removeAdUnit',function(){ - assert.isFunction(pbjs.removeAdUnit); + + it('should have function pbjs.removeAdUnit', function () { + assert.isFunction(pbjs.removeAdUnit); }); - it('should have function pbjs.requestBids',function(){ - assert.isFunction(pbjs.requestBids); + + it('should have function pbjs.requestBids', function () { + assert.isFunction(pbjs.requestBids); }); - it('should have function pbjs.addAdUnits',function(){ - assert.isFunction(pbjs.addAdUnits); + + it('should have function pbjs.addAdUnits', function () { + assert.isFunction(pbjs.addAdUnits); }); - it('should have function pbjs.addCallback',function(){ - assert.isFunction(pbjs.addCallback); + + it('should have function pbjs.addCallback', function () { + assert.isFunction(pbjs.addCallback); }); - it('should have function pbjs.removeCallback',function(){ - assert.isFunction(pbjs.removeCallback); + + it('should have function pbjs.removeCallback', function () { + assert.isFunction(pbjs.removeCallback); }); - it('should have function pbjs.aliasBidder',function(){ - assert.isFunction(pbjs.aliasBidder); + + it('should have function pbjs.aliasBidder', function () { + assert.isFunction(pbjs.aliasBidder); }); }); diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index 0abd62a8076..08c7a947f23 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -11,157 +11,162 @@ var targetingString = 'hb_bidder=rubicon&hb_adid=148018fe5e&hb_pb=10.00&foobar=3 var spyLogMessage = sinon.spy(utils, 'logMessage'); var Slot = function Slot(elementId, pathId) { - var slot = { - getSlotElementId: function getSlotElementId() { - return elementId; - }, - getAdUnitPath: function getAdUnitPath() { - return pathId; - }, - setTargeting: function setTargeting(key, value) { + var slot = { + getSlotElementId: function getSlotElementId() { + return elementId; + }, + + getAdUnitPath: function getAdUnitPath() { + return pathId; + }, + + setTargeting: function setTargeting(key, value) { } - }; - slot.spySetTargeting = sinon.spy(slot, 'setTargeting'); - return slot; + }; + slot.spySetTargeting = sinon.spy(slot, 'setTargeting'); + return slot; }; var createSlotArray = function createSlotArray() { - return [ - new Slot(config.adUnitElementIDs[0], config.adUnitCodes[0]), - new Slot(config.adUnitElementIDs[1], config.adUnitCodes[1]), - new Slot(config.adUnitElementIDs[2], config.adUnitCodes[2]) - ]; + return [ + new Slot(config.adUnitElementIDs[0], config.adUnitCodes[0]), + new Slot(config.adUnitElementIDs[1], config.adUnitCodes[1]), + new Slot(config.adUnitElementIDs[2], config.adUnitCodes[2]) + ]; }; window.googletag = { - _slots: [], - pubads: function () { - var self = this; - return { - getSlots: function () { - return self._slots; - }, - setSlots: function (slots) { - self._slots = slots; - } - } - } + _slots: [], + pubads: function () { + var self = this; + return { + getSlots: function () { + return self._slots; + }, + + setSlots: function (slots) { + self._slots = slots; + } + }; + } }; bidmanager.pbBidResponseByPlacement = bidResponses; after(function () { - utils.logMessage.restore(); + utils.logMessage.restore(); }); describe('Unit: Prebid API', function () { - describe('getAdserverTargetingForAdUnitCodeStr', function () { - it('should return targeting info as a string', function () { - var result = pbjs.getAdserverTargetingForAdUnitCodeStr(config.adUnitCodes[0]); - assert.equal(result, targetingString, 'returns expected string of ad targeting info') - }); - - it('should log message if adunitCode param is falsey', function () { - var result = pbjs.getAdserverTargetingForAdUnitCodeStr(); - assert.ok(spyLogMessage.calledWith('Need to call getAdserverTargetingForAdUnitCodeStr with adunitCode'), 'expected message was logged'); - assert.equal(result, undefined, 'result is undefined'); - }); + describe('getAdserverTargetingForAdUnitCodeStr', function () { + it('should return targeting info as a string', function () { + var result = pbjs.getAdserverTargetingForAdUnitCodeStr(config.adUnitCodes[0]); + assert.equal(result, targetingString, 'returns expected string of ad targeting info'); }); - describe('getAdserverTargetingForAdUnitCode', function () { - it('should return targeting info as an object', function () { - var result = pbjs.getAdserverTargetingForAdUnitCode(config.adUnitCodes[0]); - assert.deepEqual(result, targetingMap[config.adUnitCodes[0]], 'returns expected targeting info object'); - }); - it('should return full targeting map object if adunitCode is falsey', function () { - var result = pbjs.getAdserverTargetingForAdUnitCode(); - assert.deepEqual(result, targetingMap, 'the complete targeting map object is returned'); - }); + it('should log message if adunitCode param is falsey', function () { + var result = pbjs.getAdserverTargetingForAdUnitCodeStr(); + assert.ok(spyLogMessage.calledWith('Need to call getAdserverTargetingForAdUnitCodeStr with adunitCode'), 'expected message was logged'); + assert.equal(result, undefined, 'result is undefined'); }); + }); - describe('getAdServerTargeting', function () { - it('should call getAdServerTargetingForAdUnitCode', function () { - var spyGetAdServerTargetingForAdUnitCode = sinon.spy(pbjs, 'getAdserverTargetingForAdUnitCode'); - pbjs.getAdserverTargeting(); - assert.ok(spyGetAdServerTargetingForAdUnitCode.calledOnce, 'called the expected function'); - pbjs.getAdserverTargetingForAdUnitCode.restore(); - }); + describe('getAdserverTargetingForAdUnitCode', function () { + it('should return targeting info as an object', function () { + var result = pbjs.getAdserverTargetingForAdUnitCode(config.adUnitCodes[0]); + assert.deepEqual(result, targetingMap[config.adUnitCodes[0]], 'returns expected targeting info object'); }); - describe('getBidResponses', function () { - it('should return expected bid responses when passed an adunitCode', function () { - var result = pbjs.getBidResponses(config.adUnitCodes[0]); - var compare = require('test/fixtures/bid-responses-cloned.json')[config.adUnitCodes[0]]; + it('should return full targeting map object if adunitCode is falsey', function () { + var result = pbjs.getAdserverTargetingForAdUnitCode(); + assert.deepEqual(result, targetingMap, 'the complete targeting map object is returned'); + }); + }); + + describe('getAdServerTargeting', function () { + it('should call getAdServerTargetingForAdUnitCode', function () { + var spyGetAdServerTargetingForAdUnitCode = sinon.spy(pbjs, 'getAdserverTargetingForAdUnitCode'); + pbjs.getAdserverTargeting(); + assert.ok(spyGetAdServerTargetingForAdUnitCode.calledOnce, 'called the expected function'); + pbjs.getAdserverTargetingForAdUnitCode.restore(); + }); + }); - assert.deepEqual(result, compare); - }); - it('should return expected bid responses when not passed an adunitCode', function () { - var result = pbjs.getBidResponses(); - var compare = require('test/fixtures/bid-responses-cloned.json'); + describe('getBidResponses', function () { + it('should return expected bid responses when passed an adunitCode', function () { + var result = pbjs.getBidResponses(config.adUnitCodes[0]); + var compare = require('test/fixtures/bid-responses-cloned.json')[config.adUnitCodes[0]]; - assert.deepEqual(result, compare); - }); + assert.deepEqual(result, compare); }); - describe('getBidResponsesForAdUnitCode', function () { - it('should call getBidResponses with passed in adUnitCode', function () { - var adUnitCode = 'xyz'; - var spyGetBidResponses = sinon.spy(pbjs, 'getBidResponses'); + it('should return expected bid responses when not passed an adunitCode', function () { + var result = pbjs.getBidResponses(); + var compare = require('test/fixtures/bid-responses-cloned.json'); + + assert.deepEqual(result, compare); + }); + }); + + describe('getBidResponsesForAdUnitCode', function () { + it('should call getBidResponses with passed in adUnitCode', function () { + var adUnitCode = 'xyz'; + var spyGetBidResponses = sinon.spy(pbjs, 'getBidResponses'); + + pbjs.getBidResponsesForAdUnitCode(adUnitCode); + assert.ok(spyGetBidResponses.calledWith(adUnitCode)); + pbjs.getBidResponses.restore(); + }); + }); + + describe('setTargetingForGPTAsync', function () { + it('should log a message when googletag functions not defined', function () { + var pubads = window.googletag.pubads; + + window.googletag.pubads = undefined; + pbjs.setTargetingForAdUnitsGPTAsync(); + spyLogMessage.calledWith('window.googletag is not defined on the page'); + window.googletag.pubads = pubads; + }); - pbjs.getBidResponsesForAdUnitCode(adUnitCode); - assert.ok(spyGetBidResponses.calledWith(adUnitCode)); - pbjs.getBidResponses.restore(); - }); + it('should set targeting when passed an array of ad unit codes', function () { + var slots = createSlotArray(); + window.googletag.pubads().setSlots(slots); + + pbjs.setTargetingForGPTAsync(config.adUnitCodes); + assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_bidder', ''), 'clears hb_bidder param'); + assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_adid', ''), 'clears hb_adid param'); + assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_pb', ''), 'clears hb_pb param'); + assert.ok(slots[0].spySetTargeting.calledWithExactly('foobar', ''), 'clears foobar param'); + assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_bidder', 'rubicon'), 'sets hb_bidder param'); + assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_adid', '148018fe5e'), 'sets hb_adid param'); + assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_pb', '10.00'), 'sets hb_pb param'); + assert.ok(slots[0].spySetTargeting.calledWithExactly('foobar', '300x250'), 'sets foobar param'); }); - describe('setTargetingForGPTAsync', function () { - it('should log a message when googletag functions not defined', function() { - var pubads = window.googletag.pubads; - - window.googletag.pubads = undefined; - pbjs.setTargetingForAdUnitsGPTAsync(); - spyLogMessage.calledWith('window.googletag is not defined on the page'); - window.googletag.pubads = pubads; - }); - - it('should set targeting when passed an array of ad unit codes', function () { - var slots = createSlotArray(); - window.googletag.pubads().setSlots(slots); - - pbjs.setTargetingForGPTAsync(config.adUnitCodes); - assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_bidder', ''), 'clears hb_bidder param'); - assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_adid', ''), 'clears hb_adid param'); - assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_pb', ''), 'clears hb_pb param'); - assert.ok(slots[0].spySetTargeting.calledWithExactly('foobar', ''), 'clears foobar param'); - assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_bidder', 'rubicon'), 'sets hb_bidder param'); - assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_adid', '148018fe5e'), 'sets hb_adid param'); - assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_pb', '10.00'), 'sets hb_pb param'); - assert.ok(slots[0].spySetTargeting.calledWithExactly('foobar', '300x250'), 'sets foobar param'); - }); - - it('should set targeting from googletag data', function () { - var slots = createSlotArray(); - window.googletag.pubads().setSlots(slots); - - pbjs.setTargetingForGPTAsync(); - assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_bidder', ''), 'clears hb_bidder param'); - assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_adid', ''), 'clears hb_adid param'); - assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_pb', ''), 'clears hb_pb param'); - assert.ok(slots[0].spySetTargeting.calledWithExactly('foobar', ''), 'clears foobar param'); - assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_bidder', 'rubicon'), 'sets hb_bidder param'); - assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_adid', '148018fe5e'), 'sets hb_adid param'); - assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_pb', '10.00'), 'sets hb_pb param'); - assert.ok(slots[0].spySetTargeting.calledWithExactly('foobar', '300x250'), 'sets foobar param'); - }); + it('should set targeting from googletag data', function () { + var slots = createSlotArray(); + window.googletag.pubads().setSlots(slots); + + pbjs.setTargetingForGPTAsync(); + assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_bidder', ''), 'clears hb_bidder param'); + assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_adid', ''), 'clears hb_adid param'); + assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_pb', ''), 'clears hb_pb param'); + assert.ok(slots[0].spySetTargeting.calledWithExactly('foobar', ''), 'clears foobar param'); + assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_bidder', 'rubicon'), 'sets hb_bidder param'); + assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_adid', '148018fe5e'), 'sets hb_adid param'); + assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_pb', '10.00'), 'sets hb_pb param'); + assert.ok(slots[0].spySetTargeting.calledWithExactly('foobar', '300x250'), 'sets foobar param'); }); + }); - describe('allBidsAvailable', function() { - it('should call bidmanager.allBidsBack', function() { - var spyAllBidsBack = sinon.spy(bidmanager, 'allBidsBack'); + describe('allBidsAvailable', function () { + it('should call bidmanager.allBidsBack', function () { + var spyAllBidsBack = sinon.spy(bidmanager, 'allBidsBack'); - pbjs.allBidsAvailable(); - assert.ok(spyAllBidsBack.called, 'called bidmanager.allBidsBack'); - bidmanager.allBidsBack.restore(); - }); + pbjs.allBidsAvailable(); + assert.ok(spyAllBidsBack.called, 'called bidmanager.allBidsBack'); + bidmanager.allBidsBack.restore(); }); + }); }); diff --git a/test/spec/utils_spec.js b/test/spec/utils_spec.js index c6425473b2e..f543cb58832 100755 --- a/test/spec/utils_spec.js +++ b/test/spec/utils_spec.js @@ -1,480 +1,493 @@ -var assert = require("assert"); +var assert = require('assert'); var utils = require('../../src/utils'); -describe("Utils", function() { - - var obj_string = 's', - obj_number = 1, - obj_object = {}, - obj_array = [], - obj_function = function(){}; - - var type_string = 'String', - type_number = 'Number', - type_object = 'Object', - type_array = 'Array', - type_function = 'Function'; - - describe('replaceTokenInString', function(){ - - it('should replace all given tokens in a String', function() { - var tokensToReplace = { - 'foo': 'bar', - 'zap': 'quux' - }; - - var output = utils.replaceTokenInString("hello %FOO%, I am %ZAP%", tokensToReplace, "%"); - assert.equal(output, "hello bar, I am quux"); - }); - - it('should ignore tokens it does not see', function() { - var output = utils.replaceTokenInString("hello %FOO%", {}, "%"); - - assert.equal(output, "hello %FOO%"); - }); - }); - - describe('getBidIdParamater',function(){ - it('should return value of the key in input object',function(){ - var obj = { - 'a' : 'valueA', - 'b' : 'valueB' - }; - var output = utils.getBidIdParamater('a',obj); - assert.equal(output,'valueA'); - }); - - it('should return empty string, if the key is not existsed in the object',function(){ - var obj = { - 'a' : 'valueA', - 'b' : 'valueB' - }; - var output = utils.getBidIdParamater('c',obj); - assert.equal(output,''); - }); - }); - - describe('tryAppendQueryString',function(){ - it('should append query string to existing url',function(){ - var url = 'www.a.com?'; - var key = 'b'; - var value = 'c'; - - var output = utils.tryAppendQueryString(url, key, value); - - var expectedResult = url + key + "=" + encodeURIComponent(value) + '&'; - assert.equal(output,expectedResult); - }); - - it('should return existing url, if the value is empty',function(){ - var url = 'www.a.com?'; - var key = 'b'; - var value = ''; - - var output = utils.tryAppendQueryString(url, key, value); - assert.equal(output,url); - }); - }); - - describe('parseQueryStringParameters',function(){ - it('should append query string to existing using the input obj',function(){ - var obj={ - 'a':'1', - 'b':'2' - }; - - var output = utils.parseQueryStringParameters(obj); - var expectedResult = "a=" + encodeURIComponent('1') + "&b=" + encodeURIComponent('2') + "&"; - assert.equal(output,expectedResult); - }); - - it('should return an empty string, if input obj is empty',function(){ - var obj ={}; - var output = utils.parseQueryStringParameters(obj); - assert.equal(output,''); - }); - }); - - describe('transformAdServerTargetingObj',function(){ - it('should append query string to existing using the input obj',function(){ - var obj = { - 'a':'1', - 'b':'2' - }; - - var output = utils.transformAdServerTargetingObj(obj); - var expectedResult = "a=" + encodeURIComponent('1') + "&b=" + encodeURIComponent('2') + "&"; - assert.equal(output,expectedResult); - }); - - it('should return an empty string, if input obj is empty',function(){ - var obj ={}; - var output = utils.transformAdServerTargetingObj(obj); - assert.equal(output,''); - }); - }); - - describe('extend',function(){ - it('should merge two input object',function(){ - var target = { - 'a':'1', - 'b':'2' - }; - - var source = { - 'c':'3' - }; - - var expectedResult = { - 'a':'1', - 'b':'2', - 'c':'3' - }; - - var output = utils.extend(target, source); - assert.deepEqual(output,expectedResult); - }); - - it('should merge two input object even though target object is empty',function(){ - var target = {}; - var source = { - 'c':'3' - }; - - var output = utils.extend(target, source); - assert.deepEqual(output,source); - }); - - it('just return target object, if the source object is empty',function(){ - var target = { - 'a':'1', - 'b':'2' - }; - var source = {}; - - var output = utils.extend(target, source); - assert.deepEqual(output,target); - }); - }); - - describe('parseSizesInput',function(){ - - it('should return query string using multi size array',function(){ - var sizes = [[728, 90], [970, 90]]; - var output = utils.parseSizesInput(sizes); - assert.deepEqual(output,['728x90', '970x90']); - }); - - it('should return query string using single size array',function(){ - var sizes = [728, 90]; - var output = utils.parseSizesInput(sizes); - assert.deepEqual(output,['728x90']); - }); - - it('should return query string using string input',function(){ - var sizes = '300x250,970x90'; - var output = utils.parseSizesInput(sizes); - assert.deepEqual(output,['300x250', '970x90']); - }); - - it('return undefined if input array is empty',function(){ - var sizes =[]; - var output = utils.parseSizesInput(sizes); - assert.deepEqual(output,[]); - }); - }); - - describe('parseGPTSingleSizeArray',function(){ - - it('should return size string with input single size array',function(){ - var size = [300,250]; - var output = utils.parseGPTSingleSizeArray(size); - assert.equal(output,'300x250'); - }); - - it('should return size string with input single size array',function(){ - var size =['300','250']; - var output = utils.parseGPTSingleSizeArray(size); - assert.equal(output,'300x250'); - }); - - it('return undefined using string input',function(){ - var size ='1'; - var output = utils.parseGPTSingleSizeArray(size); - assert.equal(output,undefined); - }); - - it('return undefined using number input',function(){ - var size =1; - var output = utils.parseGPTSingleSizeArray(size); - assert.equal(output,undefined); - }); - - it('return undefined using one length single array',function(){ - var size =[300]; - var output = utils.parseGPTSingleSizeArray(size); - assert.equal(output,undefined); - }); - - it('return undefined if the input is empty',function(){ - var size =''; - var output = utils.parseGPTSingleSizeArray(size); - assert.equal(output,undefined); - }); - - it('return undefined if the input is not a number',function(){ - var size =['foo','bar']; - var output = utils.parseGPTSingleSizeArray(size); - assert.equal(output,undefined); - }); - - it('return undefined if the input is not a number 2',function(){ - var size =['foo',300]; - var output = utils.parseGPTSingleSizeArray(size); - assert.equal(output,undefined); - }); - }); - - describe('isA',function(){ - it('should return true with string object', function() { - var output = utils.isA(obj_string,type_string); - assert.deepEqual(output,true); - }); - - it('should return false with object', function() { - var output = utils.isA(obj_object,type_string); - assert.deepEqual(output,false); - }); - - it('should return true with object', function() { - var output = utils.isA(obj_object,type_object); - assert.deepEqual(output,true); - }); - - it('should return false with array object', function() { - var output = utils.isA(obj_array,type_object); - assert.deepEqual(output,false); - }); - - it('should return true with array object', function() { - var output = utils.isA(obj_array,type_array); - assert.deepEqual(output,true); - }); - - it('should return false with array object', function() { - var output = utils.isA(obj_array,type_function); - assert.deepEqual(output,false); - }); - - it('should return true with function', function() { - var output = utils.isA(obj_function,type_function); - assert.deepEqual(output,true); - }); - - it('should return false with number', function() { - var output = utils.isA(obj_function,type_number); - assert.deepEqual(output,false); - }); - - it('should return true with number', function() { - var output = utils.isA(obj_number,type_number); - assert.deepEqual(output,true); - }); - }); - - describe('isFn', function() { - it('should return true with input function',function(){ - var output = utils.isFn(obj_function); - assert.deepEqual(output,true); - }); - - it('should return false with input string',function(){ - var output = utils.isFn(obj_string); - assert.deepEqual(output,false); - }); - - it('should return false with input number',function(){ - var output = utils.isFn(obj_number); - assert.deepEqual(output,false); - }); - - it('should return false with input Array',function(){ - var output = utils.isFn(obj_array); - assert.deepEqual(output,false); - }); - - it('should return false with input object',function(){ - var output = utils.isFn(obj_object); - assert.deepEqual(output,false); - }); - }); - - describe('isStr',function(){ - it('should return true with input string',function(){ - var output = utils.isStr(obj_string); - assert.deepEqual(output,true); - }); - - it('should return false with input number',function(){ - var output = utils.isStr(obj_number); - assert.deepEqual(output,false); - }); - - it('should return false with input object',function(){ - var output = utils.isStr(obj_object); - assert.deepEqual(output,false); - }); - - it('should return false with input array',function(){ - var output = utils.isStr(obj_array); - assert.deepEqual(output,false); - }); - - it('should return false with input function',function(){ - var output = utils.isStr(obj_function); - assert.deepEqual(output,false); - }); - - }); - - describe('isArray',function(){ - it('should return false with input string',function(){ - var output = utils.isArray(obj_string); - assert.deepEqual(output,false); - }); - - it('should return false with input number',function(){ - var output = utils.isArray(obj_number); - assert.deepEqual(output,false); - }); - - it('should return false with input object',function(){ - var output = utils.isArray(obj_object); - assert.deepEqual(output,false); - }); - - it('should return true with input array',function(){ - var output = utils.isArray(obj_array); - assert.deepEqual(output,true); - }); - - it('should return false with input function',function(){ - var output = utils.isArray(obj_function); - assert.deepEqual(output,false); - }); - - }); - - - describe('isEmpty',function(){ - it('should return true with empty object',function(){ - var output = utils.isEmpty(obj_object); - assert.deepEqual(output,true); - }); - - it('should return false with non-empty object',function(){ - var obj = {'a':'b'}; - var output = utils.isEmpty(obj); - assert.deepEqual(output,false); - }); - - it('should return false with null',function(){ - var obj = null; - var output = utils.isEmpty(obj); - assert.deepEqual(output,true); - }); - }); - - describe('contains',function(){ - it('should return true if the input string contains in the input obj',function(){ - var output = utils.contains('123','1'); - assert.deepEqual(output,true); +describe('Utils', function () { + + var obj_string = 's', + obj_number = 1, + obj_object = {}, + obj_array = [], + obj_function = function () {}; + + var type_string = 'String', + type_number = 'Number', + type_object = 'Object', + type_array = 'Array', + type_function = 'Function'; + + describe('replaceTokenInString', function () { + + it('should replace all given tokens in a String', function () { + var tokensToReplace = { + foo: 'bar', + zap: 'quux' + }; + + var output = utils.replaceTokenInString('hello %FOO%, I am %ZAP%', tokensToReplace, '%'); + assert.equal(output, 'hello bar, I am quux'); + }); + + it('should ignore tokens it does not see', function () { + var output = utils.replaceTokenInString('hello %FOO%', {}, '%'); + + assert.equal(output, 'hello %FOO%'); + }); + }); + + describe('getBidIdParamater', function () { + it('should return value of the key in input object', function () { + var obj = { + a: 'valueA', + b: 'valueB' + }; + var output = utils.getBidIdParamater('a', obj); + assert.equal(output, 'valueA'); + }); + + it('should return empty string, if the key is not existsed in the object', function () { + var obj = { + a: 'valueA', + b: 'valueB' + }; + var output = utils.getBidIdParamater('c', obj); + assert.equal(output, ''); + }); + }); + + describe('tryAppendQueryString', function () { + it('should append query string to existing url', function () { + var url = 'www.a.com?'; + var key = 'b'; + var value = 'c'; + + var output = utils.tryAppendQueryString(url, key, value); + + var expectedResult = url + key + '=' + encodeURIComponent(value) + '&'; + assert.equal(output, expectedResult); + }); + + it('should return existing url, if the value is empty', function () { + var url = 'www.a.com?'; + var key = 'b'; + var value = ''; + + var output = utils.tryAppendQueryString(url, key, value); + assert.equal(output, url); + }); + }); + + describe('parseQueryStringParameters', function () { + it('should append query string to existing using the input obj', function () { + var obj = { + a:'1', + b:'2' + }; + + var output = utils.parseQueryStringParameters(obj); + var expectedResult = 'a=' + encodeURIComponent('1') + '&b=' + encodeURIComponent('2') + '&'; + assert.equal(output, expectedResult); + }); + + it('should return an empty string, if input obj is empty', function () { + var obj = {}; + var output = utils.parseQueryStringParameters(obj); + assert.equal(output, ''); + }); + }); + + describe('transformAdServerTargetingObj', function () { + it('should append query string to existing using the input obj', function () { + var obj = { + a:'1', + b:'2' + }; + + var output = utils.transformAdServerTargetingObj(obj); + var expectedResult = 'a=' + encodeURIComponent('1') + '&b=' + encodeURIComponent('2') + '&'; + assert.equal(output, expectedResult); + }); + + it('should return an empty string, if input obj is empty', function () { + var obj = {}; + var output = utils.transformAdServerTargetingObj(obj); + assert.equal(output, ''); + }); + }); + + describe('extend', function () { + it('should merge two input object', function () { + var target = { + a:'1', + b:'2' + }; + + var source = { + c:'3' + }; + + var expectedResult = { + a:'1', + b:'2', + c:'3' + }; + + var output = utils.extend(target, source); + assert.deepEqual(output, expectedResult); + }); + + it('should merge two input object even though target object is empty', function () { + var target = {}; + var source = { + c:'3' + }; + + var output = utils.extend(target, source); + assert.deepEqual(output, source); + }); + + it('just return target object, if the source object is empty', function () { + var target = { + a:'1', + b:'2' + }; + var source = {}; + + var output = utils.extend(target, source); + assert.deepEqual(output, target); + }); + }); + + describe('parseSizesInput', function () { + + it('should return query string using multi size array', function () { + var sizes = [[728, 90], [970, 90]]; + var output = utils.parseSizesInput(sizes); + assert.deepEqual(output, ['728x90', '970x90']); + }); + + it('should return query string using single size array', function () { + var sizes = [728, 90]; + var output = utils.parseSizesInput(sizes); + assert.deepEqual(output, ['728x90']); + }); + + it('should return query string using string input', function () { + var sizes = '300x250,970x90'; + var output = utils.parseSizesInput(sizes); + assert.deepEqual(output, ['300x250', '970x90']); + }); + + it('return undefined if input array is empty', function () { + var sizes = []; + var output = utils.parseSizesInput(sizes); + assert.deepEqual(output, []); + }); + }); + + describe('parseGPTSingleSizeArray', function () { + + it('should return size string with input single size array', function () { + var size = [300, 250]; + var output = utils.parseGPTSingleSizeArray(size); + assert.equal(output, '300x250'); + }); + + it('should return size string with input single size array', function () { + var size = ['300', '250']; + var output = utils.parseGPTSingleSizeArray(size); + assert.equal(output, '300x250'); + }); + + it('return undefined using string input', function () { + var size = '1'; + var output = utils.parseGPTSingleSizeArray(size); + assert.equal(output, undefined); + }); + + it('return undefined using number input', function () { + var size = 1; + var output = utils.parseGPTSingleSizeArray(size); + assert.equal(output, undefined); + }); + + it('return undefined using one length single array', function () { + var size = [300]; + var output = utils.parseGPTSingleSizeArray(size); + assert.equal(output, undefined); + }); + + it('return undefined if the input is empty', function () { + var size = ''; + var output = utils.parseGPTSingleSizeArray(size); + assert.equal(output, undefined); + }); + + it('return undefined if the input is not a number', function () { + var size = ['foo', 'bar']; + var output = utils.parseGPTSingleSizeArray(size); + assert.equal(output, undefined); + }); + + it('return undefined if the input is not a number 2', function () { + var size = ['foo', 300]; + var output = utils.parseGPTSingleSizeArray(size); + assert.equal(output, undefined); + }); + }); + + describe('isA', function () { + it('should return true with string object', function () { + var output = utils.isA(obj_string, type_string); + assert.deepEqual(output, true); + }); + + it('should return false with object', function () { + var output = utils.isA(obj_object, type_string); + assert.deepEqual(output, false); + }); + + it('should return true with object', function () { + var output = utils.isA(obj_object, type_object); + assert.deepEqual(output, true); + }); + + it('should return false with array object', function () { + var output = utils.isA(obj_array, type_object); + assert.deepEqual(output, false); + }); + + it('should return true with array object', function () { + var output = utils.isA(obj_array, type_array); + assert.deepEqual(output, true); + }); + + it('should return false with array object', function () { + var output = utils.isA(obj_array, type_function); + assert.deepEqual(output, false); + }); + + it('should return true with function', function () { + var output = utils.isA(obj_function, type_function); + assert.deepEqual(output, true); + }); + + it('should return false with number', function () { + var output = utils.isA(obj_function, type_number); + assert.deepEqual(output, false); + }); + + it('should return true with number', function () { + var output = utils.isA(obj_number, type_number); + assert.deepEqual(output, true); + }); + }); + + describe('isFn', function () { + it('should return true with input function', function () { + var output = utils.isFn(obj_function); + assert.deepEqual(output, true); + }); + + it('should return false with input string', function () { + var output = utils.isFn(obj_string); + assert.deepEqual(output, false); + }); + + it('should return false with input number', function () { + var output = utils.isFn(obj_number); + assert.deepEqual(output, false); + }); + + it('should return false with input Array', function () { + var output = utils.isFn(obj_array); + assert.deepEqual(output, false); + }); + + it('should return false with input object', function () { + var output = utils.isFn(obj_object); + assert.deepEqual(output, false); + }); + }); + + describe('isStr', function () { + it('should return true with input string', function () { + var output = utils.isStr(obj_string); + assert.deepEqual(output, true); + }); + + it('should return false with input number', function () { + var output = utils.isStr(obj_number); + assert.deepEqual(output, false); + }); + + it('should return false with input object', function () { + var output = utils.isStr(obj_object); + assert.deepEqual(output, false); + }); + + it('should return false with input array', function () { + var output = utils.isStr(obj_array); + assert.deepEqual(output, false); + }); + + it('should return false with input function', function () { + var output = utils.isStr(obj_function); + assert.deepEqual(output, false); + }); + + }); + + describe('isArray', function () { + it('should return false with input string', function () { + var output = utils.isArray(obj_string); + assert.deepEqual(output, false); + }); + + it('should return false with input number', function () { + var output = utils.isArray(obj_number); + assert.deepEqual(output, false); + }); + + it('should return false with input object', function () { + var output = utils.isArray(obj_object); + assert.deepEqual(output, false); + }); + + it('should return true with input array', function () { + var output = utils.isArray(obj_array); + assert.deepEqual(output, true); + }); + + it('should return false with input function', function () { + var output = utils.isArray(obj_function); + assert.deepEqual(output, false); + }); + + }); + + describe('isEmpty', function () { + it('should return true with empty object', function () { + var output = utils.isEmpty(obj_object); + assert.deepEqual(output, true); + }); + + it('should return false with non-empty object', function () { + var obj = { a:'b' }; + var output = utils.isEmpty(obj); + assert.deepEqual(output, false); + }); + + it('should return false with null', function () { + var obj = null; + var output = utils.isEmpty(obj); + assert.deepEqual(output, true); + }); + }); + + describe('contains', function () { + it('should return true if the input string contains in the input obj', function () { + var output = utils.contains('123', '1'); + assert.deepEqual(output, true); }); - it('should return false if the input string do not contain in the input obj',function(){ - var output = utils.contains('234','1'); - assert.deepEqual(output,false); + it('should return false if the input string do not contain in the input obj', function () { + var output = utils.contains('234', '1'); + assert.deepEqual(output, false); }); - it('should return false if the input string is empty', function() { - var output = utils.contains(); - assert.ok(!output, 'an empty string returns false'); - }); + it('should return false if the input string is empty', function () { + var output = utils.contains(); + assert.ok(!output, 'an empty string returns false'); }); + }); - describe('_map',function(){ - it('return empty array when input object is empty',function(){ - var input = {}; - var callback = function(){}; - var output = utils._map(input,callback); - assert.deepEqual(output,[]); + describe('_map', function () { + it('return empty array when input object is empty', function () { + var input = {}; + var callback = function () {}; + + var output = utils._map(input, callback); + assert.deepEqual(output, []); }); - it('return value array with vaild input object',function(){ - var input = { 'a':'A','b':'B'}; - var callback = function(v){return v;}; - var output = utils._map(input,callback); - assert.deepEqual(output,['A','B']); + it('return value array with vaild input object', function () { + var input = { a:'A', b:'B' }; + var callback = function (v) {return v;}; + + var output = utils._map(input, callback); + assert.deepEqual(output, ['A', 'B']); }); - it('return value array with vaild input object_callback func changed 1',function(){ - var input = { 'a':'A','b':'B'}; - var callback = function(v,k){return v+k;}; - var output = utils._map(input,callback); - assert.deepEqual(output,['Aa','Bb']); + it('return value array with vaild input object_callback func changed 1', function () { + var input = { a:'A', b:'B' }; + var callback = function (v, k) {return v + k;}; + + var output = utils._map(input, callback); + assert.deepEqual(output, ['Aa', 'Bb']); }); - it('return value array with vaild input object_callback func changed 2',function(){ - var input = { 'a':'A','b':'B'}; - var callback = function(v,k,o){return o;}; - var output = utils._map(input,callback); - assert.deepEqual(output,[input,input]); + it('return value array with vaild input object_callback func changed 2', function () { + var input = { a:'A', b:'B' }; + var callback = function (v, k, o) {return o;}; + + var output = utils._map(input, callback); + assert.deepEqual(output, [input, input]); }); + }); + + describe('createInvisibleIframe', function () { + var output = utils.createInvisibleIframe(); + + it('return iframe - id', function () { + assert.ok(output.id); + }); + + it('return iframe - height', function () { + assert.deepEqual(output.height, 0); }); - describe('createInvisibleIframe',function(){ - var output = utils.createInvisibleIframe(); - - it('return iframe - id',function(){ - assert.ok(output.id); - }); - it('return iframe - height',function(){ - assert.deepEqual(output.height,0); - }); - it('return iframe - width',function(){ - assert.deepEqual(output.width,0); - }); - it('return iframe - border',function(){ - assert.deepEqual(output.border,'0px'); - }); - it('return iframe - hspace',function(){ - assert.deepEqual(output.hspace,'0'); - }); - it('return iframe - vspace',function(){ - assert.deepEqual(output.vspace,'0'); - }); - it('return iframe - marginWidth',function(){ - assert.deepEqual(output.marginWidth,'0'); - }); - it('return iframe - marginHeight',function(){ - assert.deepEqual(output.marginHeight,'0'); - }); - //it('return iframe - style.border',function(){ - // assert.deepEqual(output.style.border,'0px'); - //}); - it('return iframe - scrolling',function(){ - assert.deepEqual(output.scrolling,'no'); - }); - it('return iframe - frameBorder',function(){ - assert.deepEqual(output.frameBorder,'0'); - }); - it('return iframe - src',function(){ - assert.deepEqual(output.src,'about:self'); - }); - it('return iframe - style',function(){ - assert.ok(output.style); - }); - }); + it('return iframe - width', function () { + assert.deepEqual(output.width, 0); + }); + it('return iframe - border', function () { + assert.deepEqual(output.border, '0px'); + }); + + it('return iframe - hspace', function () { + assert.deepEqual(output.hspace, '0'); + }); + + it('return iframe - vspace', function () { + assert.deepEqual(output.vspace, '0'); + }); + + it('return iframe - marginWidth', function () { + assert.deepEqual(output.marginWidth, '0'); + }); + + it('return iframe - marginHeight', function () { + assert.deepEqual(output.marginHeight, '0'); + }); + + //it('return iframe - style.border',function(){ + // assert.deepEqual(output.style.border,'0px'); + //}); + it('return iframe - scrolling', function () { + assert.deepEqual(output.scrolling, 'no'); + }); + + it('return iframe - frameBorder', function () { + assert.deepEqual(output.frameBorder, '0'); + }); + + it('return iframe - src', function () { + assert.deepEqual(output.src, 'about:self'); + }); + + it('return iframe - style', function () { + assert.ok(output.style); + }); + }); }); diff --git a/webpack.conf.js b/webpack.conf.js index a785e8e9115..5954c835f55 100644 --- a/webpack.conf.js +++ b/webpack.conf.js @@ -1,23 +1,31 @@ var RewirePlugin = require('rewire-webpack'); module.exports = { - output: { - filename: 'prebid.js' - }, - resolve: { - modulesDirectories: ['', 'node_modules', 'src'] - }, - resolveLoader: { - modulesDirectories: ['loaders', 'node_modules'] - }, - module: { - loaders: [ - { - test: /\.json$/, - loader: 'json' - } - ] - }, - plugins: [ - new RewirePlugin() + output: { + filename: 'prebid.js' + }, + resolve: { + modulesDirectories: ['', 'node_modules', 'src'] + }, + resolveLoader: { + modulesDirectories: ['loaders', 'node_modules'] + }, + module: { + loaders: [ + { + test: /\.json$/, + loader: 'json' + }, + { + test: /\.jsx?$/, + exclude: /(node_modules)/, + loader: 'babel', // 'babel-loader' is also a legal name to reference + query: { + presets: ['es2015'] + } + } ] + }, + plugins: [ + new RewirePlugin() + ] }; From bfb463be929046f087e6471c747019d04cbcf35b Mon Sep 17 00:00:00 2001 From: protonate Date: Tue, 23 Feb 2016 16:23:05 -0800 Subject: [PATCH 041/160] Source Maps Configure sourcemaps for dev build, remove and rename some node packages, favor include over exclude for test regex in conf files. update: * use exclude in karma.conf webpack postLoader for Istanbul instrumentation * remove babel pipes from gulpfile and use webpack loader for babel transforms * remove babel from gulpfile --- .jscsrc | 7 ++++--- gulpfile.js | 26 +++++++++++++------------- package.json | 7 +------ webpack.conf.js | 21 +++++++++------------ 4 files changed, 27 insertions(+), 34 deletions(-) diff --git a/.jscsrc b/.jscsrc index a06349b1b41..74a9d25cdaa 100644 --- a/.jscsrc +++ b/.jscsrc @@ -1,6 +1,7 @@ { - "maxErrors": 5000, + "maxErrors": 100, "esnext": true, - "requireTrailingComma": false, - "requireCamelCaseOrUpperCaseIdentifiers": false + "requireTrailingComma": null, + "requireCamelCaseOrUpperCaseIdentifiers": null, + "requireSpacesInAnonymousFunctionExpression": null } diff --git a/gulpfile.js b/gulpfile.js index c584d4eeaab..435c8605900 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -2,7 +2,7 @@ var argv = require('yargs').argv; var gulp = require('gulp'); var gutil = require('gulp-util'); var connect = require('gulp-connect'); -var webpack = require('gulp-webpack'); +var webpack = require('webpack-stream'); var uglify = require('gulp-uglify'); var jshint = require('gulp-jshint'); var clean = require('gulp-clean'); @@ -16,10 +16,8 @@ var concat = require('gulp-concat'); var jscs = require('gulp-jscs'); var header = require('gulp-header'); var zip = require('gulp-zip'); -var babel = require('gulp-babel'); var CI_MODE = process.env.NODE_ENV === 'ci'; -var prebidSrcLocation = './src/prebid.js'; var pkg = require('./package.json'); var dateString = 'Updated : ' + (new Date()).toISOString().substring(0, 10); var packageNameVersion = pkg.name + '_' + pkg.version; @@ -28,7 +26,7 @@ var banner = '/* <%= pkg.name %> v<%= pkg.version %> \n' + dateString + ' */\n'; // Tasks gulp.task('default', ['clean', 'quality', 'webpack']); -gulp.task('serve', ['clean', 'quality', 'webpack', 'watch', 'test']); +gulp.task('serve', ['clean', 'quality', 'devpack', 'webpack', 'watch', 'test']); gulp.task('build', ['clean', 'quality', 'webpack', 'zip']); @@ -39,6 +37,14 @@ gulp.task('clean', function () { .pipe(clean()); }); +gulp.task('devpack', function () { + webpackConfig.devtool = 'source-map'; + return gulp.src('src/**/*.js') + .pipe(webpack(webpackConfig)) + .pipe(gulp.dest('build/dev')) + .pipe(connect.reload()); +}); + gulp.task('webpack', function () { // change output filename if argument --tag given @@ -46,13 +52,11 @@ gulp.task('webpack', function () { webpackConfig.output.filename = 'prebid.' + argv.tag + '.js'; } + webpackConfig.devtool = null; + return gulp.src('src/**/*.js') .pipe(header(banner, { pkg: pkg })) .pipe(webpack(webpackConfig)) - .pipe(babel({ - presets: ['es2015'] - })) - .pipe(gulp.dest('build/dev')) .pipe(uglify()) .pipe(gulp.dest('build/dist')) .pipe(connect.reload()); @@ -73,9 +77,6 @@ gulp.task('test', function () { var browserArgs = helpers.parseBrowserArgs(argv).map(helpers.toCapitalCase); return gulp.src('lookAtKarmaConfJS') - .pipe(babel({ - presets: ['es2015'] - })) .pipe(karma({ browsers: (browserArgs.length > 0) ? browserArgs : defaultBrowsers, configFile: 'karma.conf.js', @@ -109,8 +110,7 @@ gulp.task('watch', function () { }); }); -gulp.task('quality', ['hint', 'jscs'], function () { -}); +gulp.task('quality', ['hint', 'jscs']); gulp.task('hint', function () { return gulp.src('src/**/*.js') diff --git a/package.json b/package.json index 8b35d5fd54f..acc230b692a 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,6 @@ "gulp-uglify": "^0.3.1", "gulp-util": "^3.0.0", "gulp-webdriver": "^1.0.1", - "gulp-webpack": "0.3.0", "gulp-zip": "^3.1.0", "istanbul": "^0.3.2", "istanbul-instrumenter-loader": "^0.1.2", @@ -70,15 +69,11 @@ "raw-loader": "^0.5.1", "redis": "^0.12.1", "requirejs": "^2.1.20", - "rewire": "^2.1.0", - "rewire-webpack": "~1.0", "run-sequence": "^1.1.4", "sinon": "^1.12.1", "uglify-js": "^2.4.15", - "webdriverio": "^3.2.5", "webpack": "^1.12.3", - "webpack-dev-server": "^1.12.1", - "weinre": "~2.0.0-pre-I0Z7U9OV", + "webpack-stream": "^3.1.0", "yargs": "^1.3.1" }, "dependencies": {} diff --git a/webpack.conf.js b/webpack.conf.js index 5954c835f55..9160298324c 100644 --- a/webpack.conf.js +++ b/webpack.conf.js @@ -1,31 +1,28 @@ -var RewirePlugin = require('rewire-webpack'); module.exports = { output: { filename: 'prebid.js' }, + devtool: 'source-map', resolve: { modulesDirectories: ['', 'node_modules', 'src'] }, resolveLoader: { - modulesDirectories: ['loaders', 'node_modules'] + modulesDirectories: ['node_modules'] }, module: { loaders: [ { - test: /\.json$/, - loader: 'json' - }, - { - test: /\.jsx?$/, - exclude: /(node_modules)/, + test: /\.js$/, + include: /(src|test)/, loader: 'babel', // 'babel-loader' is also a legal name to reference query: { presets: ['es2015'] } + }, + { + test: /\.json$/, + loader: 'json' } ] - }, - plugins: [ - new RewirePlugin() - ] + } }; From a763e661aaa000332e7e15c9c91e816b6eaf95c9 Mon Sep 17 00:00:00 2001 From: Eric Hochberger Date: Thu, 25 Feb 2016 12:06:16 -0500 Subject: [PATCH 042/160] switch sovrn to use height / width from the response --- src/adapters/sovrn.js | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/adapters/sovrn.js b/src/adapters/sovrn.js index 2170db52602..0b6b533b576 100644 --- a/src/adapters/sovrn.js +++ b/src/adapters/sovrn.js @@ -150,14 +150,10 @@ var SovrnAdapter = function SovrnAdapter() { //set ad content + impression url // sovrn returns '; - bid.ad_id = rubiconAd.ad_id; - bid.bidderCode = 'rubicon'; - bid.sizeId = rubiconAd.size_id; - bid.width = width; - bid.height = height; - - } else { - bid = bidfactory.createBid(2); - bid.bidderCode = 'rubicon'; - var bidObj = bidmanager.getPlacementIdByCBIdentifer(getBidId(response)); - if (bidObj) { - placementCode = bidObj.placementCode; - } - } - - } catch (e) { - utils.logError('Error parsing rubicon response bid: ' + e.message); - } - - } else { - //set bid response code to 2 = no response or error - bid = bidfactory.createBid(2); - bid.bidderCode = 'rubicon'; - var bidObj = bidmanager.getPlacementIdByCBIdentifer(getBidId(response)); - if (bidObj) { - placementCode = bidObj.placementCode; - } - - } - - //add the bid response here - bidmanager.addBidResponse(placementCode, bid); - - }; - - return { - callBids: callBids - - }; - - //end of Rubicon bid adaptor -}; - -module.exports = RubiconAdapter; From b8061ed294307017090b9a84cba4229ec66f8a0e Mon Sep 17 00:00:00 2001 From: Cliff Liang Date: Fri, 11 Mar 2016 10:51:54 -0700 Subject: [PATCH 062/160] PR#189 --- src/adapters/sovrn.js | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/src/adapters/sovrn.js b/src/adapters/sovrn.js index 0b6b533b576..f4238a9db78 100644 --- a/src/adapters/sovrn.js +++ b/src/adapters/sovrn.js @@ -15,26 +15,7 @@ var SovrnAdapter = function SovrnAdapter() { function _callBids(params) { var sovrnBids = params.bids || []; - // De-dupe by tagid then issue single bid request for all bids - _requestBids(_getUniqueTagids(sovrnBids)); - } - - // filter bids to de-dupe them? - function _getUniqueTagids(bids) { - var key; - var map = {}; - var Tagids = []; - bids.forEach(function (bid) { - map[utils.getBidIdParamater('tagid', bid.params)] = bid; - }); - - for (key in map) { - if (map.hasOwnProperty(key)) { - Tagids.push(map[key]); - } - } - - return Tagids; + _requestBids(sovrnBids); } function _requestBids(bidReqs) { From 0019934d24d7bba806bbb5bdc0166986a3bd54c7 Mon Sep 17 00:00:00 2001 From: Cliff Liang Date: Fri, 11 Mar 2016 10:52:08 -0700 Subject: [PATCH 063/160] Fixing typo --- src/adapters/sovrn.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/adapters/sovrn.js b/src/adapters/sovrn.js index f4238a9db78..0d616d54c3c 100644 --- a/src/adapters/sovrn.js +++ b/src/adapters/sovrn.js @@ -124,7 +124,7 @@ var SovrnAdapter = function SovrnAdapter() { //store bid response //bid status is good (indicating 1) bid = bidfactory.createBid(1); - bid.creative_id = sovrnBid.Id; + bid.creative_id = sovrnBid.id; bid.bidderCode = 'sovrn'; bid.cpm = responseCPM; From 4fa873d1de6a34c3832996abc147d22fe711b0fa Mon Sep 17 00:00:00 2001 From: mkendall07 Date: Mon, 14 Mar 2016 12:29:00 -0400 Subject: [PATCH 064/160] Fix for #247. Increment version --- package.json | 2 +- src/prebid.js | 37 +++++++++++++++++++++++++++++-------- src/utils.js | 17 +++++++++++++++++ 3 files changed, 47 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 6484870cd1f..be7e9aa7c55 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "0.6.1-pre", + "version": "0.7.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { diff --git a/src/prebid.js b/src/prebid.js index 525bc683674..8735d53a227 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -42,6 +42,9 @@ pbjs.logging = pbjs.logging || false; //let the world know we are loaded pbjs.libLoaded = true; +//TODO: this should be auto generated from build +utils.logInfo('Prebid.js v0.7.0 loaded'); + //create adUnit array pbjs.adUnits = pbjs.adUnits || []; @@ -353,6 +356,7 @@ function checkDefinedPlacement(id) { * @return {array} returnObj return bids array */ pbjs.getAdserverTargetingForAdUnitCodeStr = function (adunitCode) { + utils.logInfo('Invoking pbjs.getAdserverTargetingForAdUnitCodeStr', arguments); // call to retrieve bids array if (adunitCode) { var res = pbjs.getAdserverTargetingForAdUnitCode(adunitCode); @@ -368,6 +372,7 @@ pbjs.getAdserverTargetingForAdUnitCodeStr = function (adunitCode) { * @return {object} returnObj return bids */ pbjs.getAdserverTargetingForAdUnitCode = function (adunitCode) { + utils.logInfo('Invoking pbjs.getAdserverTargetingForAdUnitCode', arguments); // call to populate pb_targetingMap pbjs.getBidResponses(adunitCode); @@ -383,6 +388,7 @@ pbjs.getAdserverTargetingForAdUnitCode = function (adunitCode) { * @alias module:pbjs.getAdserverTargeting */ pbjs.getAdserverTargeting = function () { + utils.logInfo('Invoking pbjs.getAdserverTargeting', arguments); return pbjs.getAdserverTargetingForAdUnitCode(); }; @@ -393,6 +399,7 @@ pbjs.getAdserverTargeting = function () { * @return {object} map | object that contains the bidResponses */ pbjs.getBidResponses = function (adunitCode) { + utils.logInfo('Invoking pbjs.getBidResponses', arguments); var response = {}; var bidArray = []; var returnObj = {}; @@ -434,6 +441,7 @@ pbjs.getBidResponses = function (adunitCode) { * @return {Object} bidResponse object */ pbjs.getBidResponsesForAdUnitCode = function (adUnitCode) { + utils.logInfo('Invoking pbjs.getBidResponsesForAdUnitCode', arguments); return pbjs.getBidResponses(adUnitCode); }; /** @@ -442,6 +450,7 @@ pbjs.getBidResponsesForAdUnitCode = function (adUnitCode) { * @alias module:pbjs.setTargetingForAdUnitsGPTAsync */ pbjs.setTargetingForAdUnitsGPTAsync = function (codeArr) { + utils.logInfo('Invoking pbjs.setTargetingForAdUnitsGPTAsync', arguments); if (!window.googletag || !utils.isFn(window.googletag.pubads) || !utils.isFn(window.googletag.pubads().getSlots)) { utils.logError('window.googletag is not defined on the page'); return; @@ -517,6 +526,7 @@ function getTargetingfromGPTIdentifier(slot) { * @alias module:pbjs.setTargetingForGPTAsync */ pbjs.setTargetingForGPTAsync = function (codeArr) { + utils.logInfo('Invoking pbjs.setTargetingForGPTAsync', arguments); pbjs.setTargetingForAdUnitsGPTAsync(codeArr); }; @@ -526,6 +536,7 @@ pbjs.setTargetingForGPTAsync = function (codeArr) { * @return {bool} all bids available */ pbjs.allBidsAvailable = function () { + utils.logInfo('Invoking pbjs.allBidsAvailable', arguments); return bidmanager.allBidsBack(); }; @@ -536,6 +547,7 @@ pbjs.allBidsAvailable = function () { * @alias module:pbjs.renderAd */ pbjs.renderAd = function (doc, id) { + utils.logInfo('Invoking pbjs.renderAd', arguments); utils.logMessage('Calling renderAd with adId :' + id); if (doc && id) { try { @@ -586,7 +598,7 @@ pbjs.renderAd = function (doc, id) { }; -/* +/** * @deprecated - will be removed next release. Use pbjs.requestBids */ pbjs.requestBidsForAdUnit = function (adUnitCode) { @@ -618,6 +630,7 @@ pbjs.requestBidsForAdUnits = function (adUnitsObj) { * @alias module:pbjs.removeAdUnit */ pbjs.removeAdUnit = function (adUnitCode) { + utils.logInfo('Invoking pbjs.removeAdUnit', arguments); if (adUnitCode) { for (var i = 0; i < pbjs.adUnits.length; i++) { if (pbjs.adUnits[i].code === adUnitCode) { @@ -637,11 +650,9 @@ pbjs.removeAdUnit = function (adUnitCode) { * @alias module:pbjs.requestBids */ pbjs.requestBids = function (requestObj) { + utils.logInfo('Invoking pbjs.requestBids', arguments); if (!requestObj) { - //utils.logMessage('requesting all bids'); - requestAllBids(); - } else { var adUnitCodes = requestObj.adUnitCodes; var adUnits = requestObj.adUnits; @@ -678,6 +689,7 @@ pbjs.requestBids = function (requestObj) { * @alias module:pbjs.addAdUnits */ pbjs.addAdUnits = function (adUnitArr) { + utils.logInfo('Invoking pbjs.addAdUnits', arguments); if (utils.isArray(adUnitArr)) { //append array to existing pbjs.adUnits.push.apply(pbjs.adUnits, adUnitArr); @@ -702,6 +714,7 @@ pbjs.addAdUnits = function (adUnitArr) { * Currently `bidWon` is the only event that accepts an `id` parameter. */ pbjs.onEvent = function (event, handler, id) { + utils.logInfo('Invoking pbjs.onEvent', arguments); if (!utils.isFn(handler)) { utils.logError('The event handler provided is not a function and was not set on event "' + event + '".'); return; @@ -721,6 +734,7 @@ pbjs.onEvent = function (event, handler, id) { * @param {String} id an identifier in the context of the event (see `pbjs.onEvent`) */ pbjs.offEvent = function (event, handler, id) { + utils.logInfo('Invoking pbjs.offEvent', arguments); if (id && !eventValidators[event].call(null, id)) { return; } @@ -736,6 +750,7 @@ pbjs.offEvent = function (event, handler, id) { * @returns {String} id for callback */ pbjs.addCallback = function (eventStr, func) { + utils.logInfo('Invoking pbjs.addCallback', arguments); var id = null; if (!eventStr || !func || typeof func !== objectType_function) { utils.logError('error registering callback. Check method signature'); @@ -765,6 +780,7 @@ pbjs.removeCallback = function (/* cbId */) { * @return {[type]} [description] */ pbjs.registerBidAdapter = function (bidderAdaptor, bidderCode) { + utils.logInfo('Invoking pbjs.registerBidAdapter', arguments); try { adaptermanager.registerBidAdapter(bidderAdaptor(), bidderCode); } @@ -777,7 +793,7 @@ pbjs.registerBidAdapter = function (bidderAdaptor, bidderCode) { * */ pbjs.bidsAvailableForAdapter = function (bidderCode) { - +utils.logInfo('Invoking pbjs.bidsAvailableForAdapter', arguments); //TODO getAd var bids = pb_bidderMap[bidderCode].bids; @@ -812,6 +828,7 @@ pbjs.bidsAvailableForAdapter = function (bidderCode) { * @return {[type]} [description] */ pbjs.createBid = function (statusCode) { + utils.logInfo('Invoking pbjs.createBid', arguments); return bidfactory.createBid(statusCode); }; @@ -821,6 +838,7 @@ pbjs.createBid = function (statusCode) { * @param {[type]} bid [description] */ pbjs.addBidResponse = function (adUnitCode, bid) { + utils.logInfo('Invoking pbjs.addBidResponse', arguments); bidmanager.addBidResponse(adUnitCode, bid); }; @@ -830,8 +848,9 @@ pbjs.addBidResponse = function (adUnitCode, bid) { * @param {Function} callback [description] * @return {[type]} [description] */ -pbjs.loadScript = function (tagSrc, callback) { - adloader.loadScript(tagSrc, callback); +pbjs.loadScript = function (tagSrc, callback, useCache) { + utils.logInfo('Invoking pbjs.loadScript', arguments); + adloader.loadScript(tagSrc, callback, useCache); }; /** @@ -904,6 +923,7 @@ pbjs.loadScript = function (tagSrc, callback) { * @param {object} options object {provider : 'string', options : {}} */ pbjs.enableAnalytics = function (options) { + utils.logInfo('Invoking pbjs.enableAnalytics', arguments); if (!options) { utils.logError('pbjs.enableAnalytics should be called with option {}', 'prebid.js'); return; @@ -926,11 +946,12 @@ pbjs.enableAnalytics = function (options) { * This will tell analytics that all bids received after are "timed out" */ pbjs.sendTimeoutEvent = function () { + utils.logInfo('Invoking pbjs.sendTimeoutEvent', arguments); timeOutBidders(); }; pbjs.aliasBidder = function (bidderCode, alias) { - + utils.logInfo('Invoking pbjs.aliasBidder', arguments); if (bidderCode && alias) { adaptermanager.aliasBidAdapter(bidderCode, alias); } else { diff --git a/src/utils.js b/src/utils.js index d286a662eee..5f0baaf7ac3 100644 --- a/src/utils.js +++ b/src/utils.js @@ -15,6 +15,11 @@ var t_Arr = 'Array'; var t_Str = 'String'; var t_Fn = 'Function'; var toString = Object.prototype.toString; +let infoLogger = null; +try { + infoLogger = console.info.bind(window.console); +} +catch (e) {} /* * Substitutes into a string from a given map using the token @@ -172,6 +177,18 @@ exports.getTopWindowUrl = function () { } }; +exports.logInfo = function(msg, args) { + if (debugTurnedOn() && hasConsoleLogger()) { + if (infoLogger) { + if (!args || args.length === 0) { + args = ''; + } + + infoLogger('INFO: ' + msg + ((args === '') ? '' : ' : params : '), args); + } + } +}; + exports.logMessage = function (msg) { if (debugTurnedOn() && hasConsoleLogger()) { console.log('MESSAGE: ' + msg); From b86c400f96acc08d2bc561feb0d64158cf6e0827 Mon Sep 17 00:00:00 2001 From: mkendall07 Date: Thu, 10 Mar 2016 21:26:45 -0500 Subject: [PATCH 065/160] AppNexus adapter to use AST (beta) --- loaders/adapterLoader.js | 5 ++ src/adaptermanager.js | 14 +++-- src/adapters/appnexusAst.js | 104 ++++++++++++++++++++++++++++++++++++ src/adapters/baseAdapter.js | 17 ++++++ src/adloader.js | 43 +++++++++++++-- src/constants.json | 4 +- 6 files changed, 179 insertions(+), 8 deletions(-) create mode 100644 src/adapters/appnexusAst.js create mode 100644 src/adapters/baseAdapter.js diff --git a/loaders/adapterLoader.js b/loaders/adapterLoader.js index 70bf8aafc5f..a9e4301c14b 100644 --- a/loaders/adapterLoader.js +++ b/loaders/adapterLoader.js @@ -31,6 +31,11 @@ var options = { inserts = inserts.length ? inserts : files; return inserts.map((adapter) => { + if (adapter === 'appnexusAst') { + return `import { AppnexusAst } from './adapters/appnexusAst';\n` + + `exports.registerBidAdapter(new AppnexusAst('appnexus'), 'appnexus');\n`; + } + return `var ${adapterName(adapter)} = require('./adapters/${adapter}.js');\n` + `exports.registerBidAdapter(new ${adapterName(adapter)}` + `${useCreateNew(adapter)}(), '${adapter}');\n`; diff --git a/src/adaptermanager.js b/src/adaptermanager.js index 46488cc88a5..1e32c743072 100644 --- a/src/adaptermanager.js +++ b/src/adaptermanager.js @@ -4,6 +4,8 @@ var bidmanager = require('./bidmanager.js'); var utils = require('./utils.js'); var CONSTANTS = require('./constants.json'); var events = require('./events'); +import { BaseAdapter } from './adapters/baseAdapter'; + var _bidderRegistry = {}; exports.bidderRegistry = _bidderRegistry; @@ -63,9 +65,15 @@ exports.aliasBidAdapter = function (bidderCode, alias) { utils.logError('bidderCode "' + bidderCode + '" is not an existing bidder.', 'adaptermanager.aliasBidAdapter'); } else { try { - var newAdapter = bidAdaptor.createNew(); - newAdapter.setBidderCode(alias); - this.registerBidAdapter(newAdapter, alias); + let newAdapter = null; + if(bidAdaptor instanceof BaseAdapter) { + //newAdapter = new bidAdaptor.constructor(alias); + utils.logError(bidderCode + ' bidder does not currently support aliasing.', 'adaptermanager.aliasBidAdapter'); + } else { + newAdapter = bidAdaptor.createNew(); + newAdapter.setBidderCode(alias); + this.registerBidAdapter(newAdapter, alias); + } } catch (e) { utils.logError(bidderCode + ' bidder does not currently support aliasing.', 'adaptermanager.aliasBidAdapter'); } diff --git a/src/adapters/appnexusAst.js b/src/adapters/appnexusAst.js new file mode 100644 index 00000000000..a27107b85fb --- /dev/null +++ b/src/adapters/appnexusAst.js @@ -0,0 +1,104 @@ +import { BaseAdapter } from './baseAdapter'; +const utils = require('../utils'); +const adloader = require('../adloader.js'); +const bidmanager = require('../bidmanager'); +const bidfactory = require('../bidfactory'); +const CONSTANTS = require('../constants.json'); + +const AST_URL = 'https://acdn.adnxs.com/ast/alpha/ast.js'; + +export class AppnexusAst extends BaseAdapter { + constructor(code) { + super(code); + this._bidRequests = null; + } + + callBids(params) { + window.apntag = window.apntag || {}; + window.apntag.anq = window.apntag.anq || []; + this._bidRequests = params.bids; + adloader.loadScript(AST_URL, () => { + this._requestAds(this.code); + }, true); + } + + _requestAds(code) { + if (utils.debugTurnedOn()) { + window.apntag.debug = true; + } + + window.apntag.clearRequest(); + + for (let bidRequest of this._bidRequests) { + const astTag = this._buildTag(bidRequest); + const requestTag = window.apntag.defineTag(astTag); + const placementCode = bidRequest.placementCode; + + //successful bid + /*jshint -W083 */ + requestTag.on('adAvailable', function(ad) { + const bid = bidfactory.createBid(CONSTANTS.STATUS.GOOD); + bid.code = code; + bid.bidderCode = code; + bid.creative_id = ad.creativeId; + bid.cpm = ad.cpm; + bid.ad = ad.banner.content; + try { + const url = ad.banner.trackers[0].impression_urls[0]; + const tracker = utils.createTrackPixelHtml(url); + bid.ad += tracker; + } catch (e) { + utils.logError('Error appending tracking pixel', 'appnexusAst.js:_requestAds', e); + } + + bid.width = ad.banner.width; + bid.height = ad.banner.height; + bidmanager.addBidResponse(placementCode, bid); + }); + + //no bid + requestTag.on('adNoBid', function() { + const bid = bidfactory.createBid(CONSTANTS.STATUS.NO_BID); + bid.code = code; + bid.bidderCode = code; + bidmanager.addBidResponse(placementCode, bid); + }); + } + + window.apntag.loadTags(); + } + + _buildTag(bid) { + let tag = {}; + const uuid = utils.getUniqueIdentifierStr(); + + //clone bid.params to tag + const jsonBid = JSON.stringify(bid.params); + tag = JSON.parse(jsonBid); + + //append member if available. Should not use multiple member IDs. + utils._each(tag, function(value, key) { + if (key === 'member') { + window.apntag.setPageOpts({ + member: value + }); + } + + if (key === 'keywords') { + window.apntag.setPageOpts({ + keywords: value + }); + } + }); + + tag.targetId = uuid; + tag.prebid = true; + if (!tag.sizes) { + tag.sizes = bid.sizes; + } + + tag.tagId = Number.parseInt(bid.params.placementId); + tag.disablePsa = true; + return tag; + } +} diff --git a/src/adapters/baseAdapter.js b/src/adapters/baseAdapter.js new file mode 100644 index 00000000000..819436b4fee --- /dev/null +++ b/src/adapters/baseAdapter.js @@ -0,0 +1,17 @@ +export class BaseAdapter { + constructor(code) { + this.code = code; + } + + getCode() { + return this.code; + } + + setCode(code) { + this.code = code; + } + + callBids() { + throw 'adapter implementation must override callBids method'; + } +} diff --git a/src/adloader.js b/src/adloader.js index f3153cb700e..509f222c5c3 100644 --- a/src/adloader.js +++ b/src/adloader.js @@ -1,12 +1,50 @@ var utils = require('./utils'); +let _requestCache = {}; //add a script tag to the page, used to add /jpt call to page -exports.loadScript = function (tagSrc, callback) { +exports.loadScript = function (tagSrc, callback, cacheRequest) { if (!tagSrc) { utils.logError('Error attempting to request empty URL', 'adloader.js:loadScript'); return; } + if (cacheRequest) { + if (_requestCache[tagSrc]) { + if (_requestCache[tagSrc].loaded) { + //invokeCallbacks immediately + callback(); + } else { + //queue the callback + _requestCache[tagSrc].callbacks.push(callback); + } + } else { + _requestCache[tagSrc] = { + loaded:false, + callbacks:[] + }; + _requestCache[tagSrc].callbacks.push(callback); + requestResource(tagSrc, function() { + _requestCache[tagSrc].loaded = true; + try { + for (let i = 0; i < _requestCache[tagSrc].callbacks.length; i++) { + _requestCache[tagSrc].callbacks[i](); + } + } + catch (e) { + utils.logError('Error executing callback', 'adloader.js:loadScript', e); + } + }); + } + } + + //trigger one time request + else { + requestResource(tagSrc, callback); + } + +}; + +function requestResource(tagSrc, callback) { var jptScript = document.createElement('script'); jptScript.type = 'text/javascript'; jptScript.async = true; @@ -27,7 +65,6 @@ exports.loadScript = function (tagSrc, callback) { } } - //call function to build the JPT call jptScript.src = tagSrc; //add the new script tag to the page @@ -37,7 +74,7 @@ exports.loadScript = function (tagSrc, callback) { elToAppend = elToAppend[0]; elToAppend.insertBefore(jptScript, elToAppend.firstChild); } -}; +} //track a impbus tracking pixel //TODO: Decide if tracking via AJAX is sufficent, or do we need to diff --git a/src/constants.json b/src/constants.json index 9358c4ee825..865c91d4694 100644 --- a/src/constants.json +++ b/src/constants.json @@ -11,8 +11,8 @@ }, "DEBUG_MODE": "pbjs_debug", "STATUS": { - "GOOD": "good", - "TIMEOUT": "timed out" + "GOOD": 1, + "NO_BID": 2 }, "CB": { "TYPE": { From 636c95c5cb429ed4c8b69c5776703e6e884c4b2d Mon Sep 17 00:00:00 2001 From: protonate Date: Mon, 14 Mar 2016 12:43:56 -0700 Subject: [PATCH 066/160] Add triplelift to adapters array i* Add `triplelift` to `adapters` array in package.json * TripleLife adapter params in example page * Code style reformat of example page --- integrationExamples/gpt/pbjs_example_gpt.html | 672 ++++++++++-------- package.json | 1 + 2 files changed, 358 insertions(+), 315 deletions(-) diff --git a/integrationExamples/gpt/pbjs_example_gpt.html b/integrationExamples/gpt/pbjs_example_gpt.html index 765320d89a6..030d3489c99 100644 --- a/integrationExamples/gpt/pbjs_example_gpt.html +++ b/integrationExamples/gpt/pbjs_example_gpt.html @@ -2,322 +2,353 @@ - - - - - console.log('Are all bids available? : ' + pbjs.allBidsAvailable()); - console.log('Targeting params:') - console.log(pbjs.getAdserverTargeting()); + @@ -325,33 +356,44 @@ -

    Prebid.js Test

    +

    Prebid.js Test

    -
    - -
    +
    + +
    -
    - -
    +
    + +
    '; + var RUBICON_CREATIVE_START = ''; // pre-initialize the rubicon object // needs to be attached to the window @@ -153,7 +153,7 @@ var RubiconAdapter = function RubiconAdapter() { */ function _initSDK(options, done) { if (RUBICON_INITIALIZED) { - return; + return; } RUBICON_INITIALIZED = 1; @@ -203,10 +203,10 @@ var RubiconAdapter = function RubiconAdapter() { var keywords = bid.params.keywords || []; var inventory = bid.params.inventory || []; var slot = window.rubicontag.defineSlot({ - siteId : bid.params.siteId, - zoneId : bid.params.zoneId, - sizes : bid.params.sizes, - id : bid.placementCode + siteId: bid.params.siteId, + zoneId: bid.params.zoneId, + sizes: bid.params.sizes, + id: bid.placementCode }); slot.clearTargeting(); @@ -266,7 +266,7 @@ var RubiconAdapter = function RubiconAdapter() { } // on the first bid, set up the SDK - if ( ! RUBICON_INITIALIZED) { + if (!RUBICON_INITIALIZED) { _initSDK(params.bids[0].params); } @@ -278,8 +278,8 @@ var RubiconAdapter = function RubiconAdapter() { slots.push(_defineSlot(bids[key])); } - var parameters = {slots : slots}; - var callback = function (){ + var parameters = { slots: slots }; + var callback = function () { _bidsReady(slots); }; @@ -296,4 +296,4 @@ var RubiconAdapter = function RubiconAdapter() { }; }; -module.exports = RubiconAdapter; \ No newline at end of file +module.exports = RubiconAdapter; From d9a13b94d4e71e285e95b8cd458c4aba04d1a717 Mon Sep 17 00:00:00 2001 From: Matan Arbel Date: Wed, 30 Mar 2016 11:26:15 +0300 Subject: [PATCH 074/160] Create adapter for Brightcom --- integrationExamples/gpt/pbjs_example_gpt.html | 24 +- package.json | 3 +- src/adapters/brightcom.js | 214 ++++++++++++++++++ 3 files changed, 234 insertions(+), 7 deletions(-) create mode 100644 src/adapters/brightcom.js diff --git a/integrationExamples/gpt/pbjs_example_gpt.html b/integrationExamples/gpt/pbjs_example_gpt.html index 030d3489c99..b433fceeab3 100644 --- a/integrationExamples/gpt/pbjs_example_gpt.html +++ b/integrationExamples/gpt/pbjs_example_gpt.html @@ -127,6 +127,12 @@ supplyPartnerId: 1, test: true // only include when testing } + }, + { + bidder: 'brightcom', + params: { + tagId: 16577 // Tag ID supplied by Brightcom - brightcom.com + } } ] }, { @@ -186,12 +192,18 @@ tier3SiteID: 1234 //Number - Optional } }, - { - bidder: 'triplelift', - params: { - inventoryCode: 'sortable_all_right_sports' - } - }] + { + bidder: 'triplelift', + params: { + inventoryCode: 'sortable_all_right_sports' + } + }, + { + bidder: 'brightcom', + params: { + tagId: 16577 // Tag ID supplied by Brightcom - brightcom.com + } + }] } ]; diff --git a/package.json b/package.json index 1719f4e92ff..5ea2ce82d62 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,8 @@ "springserve", "triplelift", "yieldbot", - "nginad" + "nginad", + "brightcom" ], "author": "the prebid.js contributors", "license": "Apache-2.0", diff --git a/src/adapters/brightcom.js b/src/adapters/brightcom.js new file mode 100644 index 00000000000..2abebb3bab1 --- /dev/null +++ b/src/adapters/brightcom.js @@ -0,0 +1,214 @@ +var CONSTANTS = require('../constants.json'); +var utils = require('../utils.js'); +var bidfactory = require('../bidfactory.js'); +var bidmanager = require('../bidmanager.js'); +var adloader = require('../adloader'); + +/** + * Adapter for requesting bids from Brightcom + */ +var BrightcomAdapter = function BrightcomAdapter() { + + // Set Brightcom Bidder URL + var brightcomUrl = 'hb.iselephant.com/auc/ortb'; + + // Define the bidder code + var brightcomBidderCode = 'brightcom'; + + // Define the callback function + var brightcomCallbackFunction = 'window.pbjs=window.pbjs||window.parent.pbjs||window.top.pbjs;window.pbjs.brightcomResponse'; + + // Manage the requested and received ad units' codes, to know which are invalid (didn't return) + var reqAdUnitsCode = [], + resAdUnitsCode = []; + + function _callBids(params) { + + var bidRequests = params.bids || []; + + // Get page data + var siteDomain = window.location.host; + var sitePage = window.location.pathname + location.search + location.hash; + + // Prepare impressions object + var brightcomImps = []; + + // Prepare a variable for publisher id + var pubId = ''; + + // Go through the requests and build array of impressions + utils._each(bidRequests, function(bid) { + + // Get impression details + var tagId = utils.getBidIdParamater('tagId', bid.params); + var ref = utils.getBidIdParamater('ref', bid.params); + var adWidth=0; + var adHeight=0; + + // If no publisher id is set, use the current + if (pubId === '') { + // Get the current publisher id (if it doesn't exist, it'll return '') + pubId = utils.getBidIdParamater('pubId', bid.params); + } + + // Brightcom supports only 1 size per impression + // Check if the array contains 1 size or array of sizes + if (bid.sizes.length === 2 && typeof bid.sizes[0] === 'number' && typeof bid.sizes[1] === 'number') { + // The array contains 1 size (the items are the values) + adWidth = bid.sizes[0]; + adHeight = bid.sizes[1]; + } else { + // The array contains array of sizes, use the first size + adWidth = bid.sizes[0][0]; + adHeight = bid.sizes[0][1]; + } + + // Build the impression + var imp = { + id: utils.getUniqueIdentifierStr(), + banner: { + w: adWidth, + h: adHeight + }, + tagid: tagId + }; + + // If ref exists, create it (in the "ext" object) + if (ref !== '') { + imp.ext = { + refoverride: ref + }; + } + + // Add current impression to collection + brightcomImps.push(imp); + // Add mapping to current bid via impression id + bidmanager.pbCallbackMap[imp.id] = bid; + + // Add current ad unit's code to tracking + reqAdUnitsCode.push(bid.placementCode); + + }); + + // Build the bid request + var brightcomBidReq = { + id: utils.getUniqueIdentifierStr(), + imp: brightcomImps, + site:{ + publisher: { + id: pubId + }, + domain: siteDomain, + page: sitePage + } + }; + + // Add timeout data, if available + var PREBID_TIMEOUT = PREBID_TIMEOUT || 0; + var curTimeout = PREBID_TIMEOUT; + if (curTimeout > 0) { + brightcomBidReq.tmax = curTimeout; + } + + // Define the bid request call URL + var bidRequestCallUrl = 'https://' + brightcomUrl + + '?callback=' + brightcomCallbackFunction + + '&request=' + encodeURIComponent(JSON.stringify(brightcomBidReq)); + + // Add the call to get the bid + adloader.loadScript(bidRequestCallUrl, null); + + } + + //expose the callback to the global object: + pbjs.brightcomResponse = function(brightcomResponseObj) { + + var bid = {}; + + // Make sure response is valid + if ( + (brightcomResponseObj) && (brightcomResponseObj.id) && + (brightcomResponseObj.seatbid) && (brightcomResponseObj.seatbid.length !== 0) && + (brightcomResponseObj.seatbid[0].bid) && (brightcomResponseObj.seatbid[0].bid.length !== 0) + ) { + + // Go through the received bids + brightcomResponseObj.seatbid[0].bid.forEach( function(curBid) { + + // Get the bid request data + var bidRequest = bidmanager.getPlacementIdByCBIdentifer(curBid.impid); + + // Make sure the bid exists + if (bidRequest) { + + var placementCode = bidRequest.placementCode; + bidRequest.status = CONSTANTS.STATUS.GOOD; + + curBid.placementCode = placementCode; + curBid.size = bidRequest.sizes; + + // Get the creative + var responseCreative = curBid.adm; + // Build the NURL element + var responseNurl = ''; + // Build the ad to display: + var responseAd = decodeURIComponent(responseCreative + responseNurl); + + // Create a valid bid + bid = bidfactory.createBid(1); + + // Set the bid data + bid.creative_id = curBid.Id; + bid.bidderCode = brightcomBidderCode; + bid.cpm = parseFloat(curBid.price); + + // Brightcom tag is in + -

    Prebid.js Test

    +

    Prebid.js Test

    -
    - -
    +
    + +
    -
    - -
    +
    + +
    + Bidders all have different recommended ad server line item targeting and + creative setup. To remove the headache for you, Prebid.js has a default + recommended query string targeting setting for all bidders. + If you’d like to customize the key value pairs, you can overwrite the settings + as the below example shows. Let your ad ops team know about the change, so they + can update the line item targeting accordingly. - - console.log('Targeting params:'); - console.log(pbjs.getAdserverTargeting()); + + @@ -418,7 +396,7 @@

    Prebid.js Test

    })(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga'); ga('create', 'XXXXXX', 'auto'); - //Send data from prebid.js automatically + //Send data from prebid.js automatically pbjs.que.push(function () { pbjs.enableAnalytics({ provider: 'ga', diff --git a/integrationExamples/gpt/pbjs_partial_refresh_gpt.html b/integrationExamples/gpt/pbjs_partial_refresh_gpt.html index b183ba91adc..44e649c1136 100644 --- a/integrationExamples/gpt/pbjs_partial_refresh_gpt.html +++ b/integrationExamples/gpt/pbjs_partial_refresh_gpt.html @@ -8,25 +8,25 @@ var googletag = googletag || {}; googletag.cmd = googletag.cmd || []; - + googletag.cmd.push(function () { + googletag.pubads().disableInitialLoad(); + }); + /* pbjs.initAdserver will be called either when all bids are back, or - when the timeout is reached. - */ + when the timeout is reached. + */ function initAdserver() { if (pbjs.initAdserverSet) return; - //load GPT library here - (function() { - var gads = document.createElement('script'); - gads.async = true; - gads.type = 'text/javascript'; - var useSSL = 'https:' == document.location.protocol; - gads.src = (useSSL ? 'https:' : 'http:') + - '//www.googletagservices.com/tag/js/gpt.js'; - var node = document.getElementsByTagName('script')[0]; - node.parentNode.insertBefore(gads, node); - })(); + + googletag.cmd.push(function () { + pbjs.que.push(function () { + pbjs.setTargetingForGPTAsync(); + googletag.pubads().refresh(); + }); + }); pbjs.initAdserverSet = true; - }; + } + // Load GPT when timeout is reached. setTimeout(initAdserver, PREBID_TIMEOUT); @@ -35,7 +35,7 @@ // Load the Prebid Javascript Library Async. We recommend loading it immediately after // the initAdserver() and setTimeout functions. - (function() { + (function () { var d = document, pbs = d.createElement("script"), pro = d.location.protocal; pbs.type = "text/javascript"; pbs.src = '/build/dist/prebid.js'; @@ -43,260 +43,252 @@ target.insertBefore(pbs, target.firstChild); })(); + pbjs.que.push(function () { - pbjs.que.push(function(){ - - /* 1. Register bidder tag Ids + /* 1. Register bidder tag Ids - Registers the bidder tags for your ad units. Once the prebid.js - library loads, it reads the pbjs.adUnits object and sends out - bid requests. Find the complete reference on bidders at - http://prebid.org/bidders.html. + Registers the bidder tags for your ad units. Once the prebid.js + library loads, it reads the pbjs.adUnits object and sends out + bid requests. Find the complete reference on bidders at + http://prebid.org/bidders.html. - code: Your GPT slot’s ad unit path. If they don’t match, prebid.js - would not be able to set targeting correctly - sizes: All sizes your ad unit accepts. They should match with GPT. + code: Your GPT slot’s ad unit path. If they don’t match, prebid.js + would not be able to set targeting correctly + sizes: All sizes your ad unit accepts. They should match with GPT. - */ - var adUnits = [{ - code: '/9968336/header-bid-tag-0', - sizes: [[300, 250], [300, 600]], - bids: [ - // 1 ad unit can be targeted by multiple bids. + */ + var adUnits = [ { - bidder: 'appnexus', - params: { - placementId: 'TO ADD' - } - }, - // 1 ad unit can also be targeted by multiple bids from 1 bidder - { - bidder: 'pubmatic', - params: { - publisherId: 'TO ADD', - adSlot: 'TO ADD@300x250' - } + code: '/9968336/header-bid-tag-0', + sizes: [[300, 250], [300, 600]], + bids: [ + // 1 ad unit can be targeted by multiple bids. + { + bidder: 'appnexus', + params: { + placementId: 'TO ADD' + } + }, + // 1 ad unit can also be targeted by multiple bids from 1 bidder + { + bidder: 'pubmatic', + params: { + publisherId: 'TO ADD', + adSlot: 'TO ADD@300x250' + } + }, { + bidder: 'pubmatic', + params: { + publisherId: 'TO ADD', + adSlot: 'TO ADD@300x600' + } + } + ] }, { - bidder: 'pubmatic', - params: { - publisherId: 'TO ADD', - adSlot: 'TO ADD@300x600' - } - }] - },{ - code: '/9968336/header-bid-tag1', - sizes: [[728, 90], [970, 90]], - bids: [{ - bidder: 'appnexus', - params: { - placementId: 'TO ADD' - } - },{ - bidder: 'openx', - params: { - pgid: 'TO ADD', - unit: 'TO ADD', - jstag_url : 'TO ADD' - } - },{ - bidder: 'rubicon', - params: { - accountId: "1234", //String - required - siteId: "1234", //String - required - zoneId: "1234", //String - required - sizes: [15], //Array[Number] - optional - userId: "12345", //String - optional - keywords: ["a","b","c"], //Array[String] - optional - inventory: { //Object - optional - rating:"5-star", - prodtype:"tech" + code: '/9968336/header-bid-tag1', + sizes: [[728, 90], [970, 90]], + bids: [ + { + bidder: 'appnexus', + params: { + placementId: 'TO ADD' + } + }, { + bidder: 'openx', + params: { + pgid: 'TO ADD', + unit: 'TO ADD', + jstag_url: 'TO ADD' + } + }, { + bidder: 'rubicon', + params: { + accountId: "1234", //String - required + siteId: "1234", //String - required + zoneId: "1234", //String - required + sizes: [15], //Array[Number] - optional + userId: "12345", //String - optional + keywords: ["a", "b", "c"], //Array[String] - optional + inventory: { //Object - optional + rating: "5-star", + prodtype: "tech" + }, + visitor: { //Object - optional + ucat: "new", + search: "iphone" + } + } + }, { + bidder: 'yieldbot', + params: { + pub: 'TO ADD', + name: 'TO ADD' + } }, - visitor: { //Object - optional - ucat:"new", - search:"iphone" + { + bidder: 'casale', + params: { + slotId: 'TO ADD', // number + casaleUrl: 'TO ADD' + } } - } - },{ - bidder: 'yieldbot', - params: { - pub: 'TO ADD', - name: 'TO ADD' - } - }, - { - bidder: 'casale', - params: { - slotId: 'TO ADD', // number - casaleUrl: 'TO ADD' - } - }] - }]; + ] + } + ]; - //add the adUnits - pbjs.addAdUnits(adUnits); + //add the adUnits + pbjs.addAdUnits(adUnits); - //register a callback handler - pbjs.addCallback('adUnitBidsBack', function(adUnitCode){ + //register a callback handler + pbjs.addCallback('adUnitBidsBack', function (adUnitCode) { console.log('ad unit bids back for : ' + adUnitCode); }); - /* Request bids for the added ad units. If adUnits or adUnitCodes are - not specified, the function will request bids for all added ad units. - */ - pbjs.requestBids({ + /* Request bids for the added ad units. If adUnits or adUnitCodes are + not specified, the function will request bids for all added ad units. + */ + pbjs.requestBids({ /* The bidsBack function will be called when either timeout is - reached, or when all bids come back, whichever happens sooner. - */ - bidsBackHandler: function(bidResponses) { + reached, or when all bids come back, whichever happens sooner. + */ + bidsBackHandler: function (bidResponses) { initAdserver(); }, /* You can specify specific `adUnitCodes` to only request bids - for certain ad units. - adUnitCodes: ['code1', 'code2'] - */ + for certain ad units. + adUnitCodes: ['code1', 'code2'] + */ - /* You can also make one off bid requests for the given `adUnits`. - adUnits: [adUnit2, adUnit1] - */ + /* You can also make one off bid requests for the given `adUnits`. + adUnits: [adUnit2, adUnit1] + */ /* The bidsBackHandler will be executed either when all bids are - back, or when the timeout is reached. - timeout: 1000 - */ - }) + back, or when the timeout is reached. + timeout: 1000 + */ + }) - - /* 2. Configure Ad Server Targeting + /* 2. Configure Ad Server Targeting - The below section defines what key value targeting will be sent to GPT. - For each bidder’s bid, Prebid.js will set the below 4 keys (hb_bidder, - hb_adid, hb_pb, hb_size) with their corresponding values. + The below section defines what key value targeting will be sent to GPT. + For each bidder’s bid, Prebid.js will set the below 4 keys (hb_bidder, + hb_adid, hb_pb, hb_size) with their corresponding values. - Bidders all have different recommended ad server line item targeting and - creative setup. To remove the headache for you, Prebid.js has a default - recommended query string targeting setting for all bidders. + Bidders all have different recommended ad server line item targeting and + creative setup. To remove the headache for you, Prebid.js has a default + recommended query string targeting setting for all bidders. - If you’d like to customize the key value pairs, you can overwrite the settings - as the below example shows. Let your ad ops team know about the change, so they - can update the line item targeting accordingly. + If you’d like to customize the key value pairs, you can overwrite the settings + as the below example shows. Let your ad ops team know about the change, so they + can update the line item targeting accordingly. + + */ - */ - pbjs.bidderSettings = { standard: { - adserverTargeting: [{ + adserverTargeting: [ + { key: "hb_bidder", - val: function(bidResponse) { + val: function (bidResponse) { return bidResponse.bidderCode; } }, { key: "hb_adid", - val: function(bidResponse) { + val: function (bidResponse) { return bidResponse.adId; } }, { key: "hb_pb", - val: function(bidResponse) { + val: function (bidResponse) { return bidResponse.pbLg; } - }] - } + } + ] + } }; -}); + }); + - + -

    Prebid.js Test

    +

    Prebid.js Test

    -
    Div-1, 300x250 or 300x600
    +
    Div-1, 300x250 or 300x600
    - + -
    - -
    +
    + +
    -
    Div-2, 728x90 or 970x90
    +
    Div-2, 728x90 or 970x90
    - + -
    - -
    +
    + +
    From 209a892718d345fe275c97676801ffdde8470c1b Mon Sep 17 00:00:00 2001 From: Bret Gorsline Date: Wed, 6 Apr 2016 14:44:31 -0400 Subject: [PATCH 083/160] defineSlot was picking up object globals --- src/adapters/rubicon.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/adapters/rubicon.js b/src/adapters/rubicon.js index f001a8e8ace..51368bf066c 100644 --- a/src/adapters/rubicon.js +++ b/src/adapters/rubicon.js @@ -220,11 +220,15 @@ var RubiconAdapter = function RubiconAdapter() { } for (var key in visitor) { - slot.addFPV(key, visitor[key]); + if (visitor.hasOwnProperty(key)) { + slot.addFPV(key, visitor[key]); + } } for (var key in inventory) { - slot.addFPI(key, inventory[key]); + if (inventory.hasOwnProperty(key)) { + slot.addFPI(key, inventory[key]); + } } slot.addKW(keywords); From c1ae8a610b94b8b9378ab179ea143285f4670d5e Mon Sep 17 00:00:00 2001 From: Steven Hadfield Date: Mon, 4 Apr 2016 14:25:24 -0700 Subject: [PATCH 084/160] Use utils.isEmpty to handle check for empty pbBidResponseByPlacement values --- src/bidmanager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bidmanager.js b/src/bidmanager.js index 451f2527819..a01462469a0 100644 --- a/src/bidmanager.js +++ b/src/bidmanager.js @@ -168,7 +168,7 @@ exports.addBidResponse = function (adUnitCode, bid) { } //store by placement ID - if (adUnitCode && pbBidResponseByPlacement[adUnitCode]) { + if (adUnitCode && pbBidResponseByPlacement[adUnitCode] && !utils.isEmpty(pbBidResponseByPlacement[adUnitCode])) { //update bid response object bidResponseObj = pbBidResponseByPlacement[adUnitCode]; From dddacc4ccc701d5818d3635697045a9c836706ce Mon Sep 17 00:00:00 2001 From: Matan Arbel Date: Thu, 7 Apr 2016 11:52:25 +0300 Subject: [PATCH 085/160] Brightcom adapter - update the page url parameter --- src/adapters/brightcom.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/adapters/brightcom.js b/src/adapters/brightcom.js index 2abebb3bab1..76d3bbdd8b4 100644 --- a/src/adapters/brightcom.js +++ b/src/adapters/brightcom.js @@ -28,7 +28,7 @@ var BrightcomAdapter = function BrightcomAdapter() { // Get page data var siteDomain = window.location.host; - var sitePage = window.location.pathname + location.search + location.hash; + var sitePage = window.location.href; // Prepare impressions object var brightcomImps = []; From 12a081a07b056aa4277095ff8ad391878028e633 Mon Sep 17 00:00:00 2001 From: "charles.black" Date: Thu, 7 Apr 2016 13:35:10 -0400 Subject: [PATCH 086/160] Refactor how expected bids is determined to more accurately represent the expected and add support for multiple size slots --- integrationExamples/gpt/pbjs_example_gpt.html | 2 - src/adapters/indexExchange.js | 237 +++++++++++------- 2 files changed, 151 insertions(+), 88 deletions(-) diff --git a/integrationExamples/gpt/pbjs_example_gpt.html b/integrationExamples/gpt/pbjs_example_gpt.html index 498b1462db9..c9e5edfa415 100644 --- a/integrationExamples/gpt/pbjs_example_gpt.html +++ b/integrationExamples/gpt/pbjs_example_gpt.html @@ -206,8 +206,6 @@ id: 'TO ADD', //String - required siteID: 123, //Number - required timeout: 456, //Number - Optional - tier2SiteID: 789, //Number - Optional - tier3SiteID: 1234 //Number - Optional } }, { diff --git a/src/adapters/indexExchange.js b/src/adapters/indexExchange.js index 05ce59a46f3..b66063071aa 100644 --- a/src/adapters/indexExchange.js +++ b/src/adapters/indexExchange.js @@ -8,7 +8,6 @@ var adloader = require('../adloader.js'); var ADAPTER_NAME = 'INDEXEXCHANGE'; var ADAPTER_CODE = 'indexExchange'; -var cygnus_index_primary_request = true; var cygnus_index_parse_res = function () { }; @@ -17,6 +16,8 @@ window.cygnus_index_args = {}; var cygnus_index_adunits = [[728, 90], [120, 600], [300, 250], [160, 600], [336, 280], [234, 60], [300, 600], [300, 50], [320, 50], [970, 250], [300, 1050], [970, 90], [180, 150]]; // jshint ignore:line var cygnus_index_start = function () { + window.index_slots = []; + window.cygnus_index_args.parseFn = cygnus_index_parse_res; var escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; var meta = { @@ -237,6 +238,7 @@ var cygnus_index_start = function () { return req.buildRequest(); } catch (e) { + utils.logError('Error calling index adapter', ADAPTER_NAME, e); } }; @@ -251,18 +253,6 @@ var IndexExchangeAdapter = function IndexExchangeAdapter() { var firstAdUnitCode = ''; function _callBids(request) { - /* - Function in order to add a slot into the list if it hasn't been created yet, else it returns the same list. - */ - function mergeSlotInto(slot,slotList){ - for(var i = 0;i 20) { + utils.logError('Error calling index adapter, too many unique slot-sizes. Only 20 supported.', ADAPTER_NAME); + return; + } + bidmanager.setExpectedBidsCount(ADAPTER_CODE, expectedBids); adloader.loadScript(cygnus_index_start()); + var responded = false; + + // Handle response window.cygnus_index_ready_state = function () { + if (responded) { + return; + } + responded = true; + try { var indexObj = _IndexRequestData.targetIDToBid; var lookupObj = cygnus_index_args; - if (utils.isEmpty(indexObj)) { - var bid = bidfactory.createBid(2); - bid.bidderCode = ADAPTER_CODE; - logErrorBidResponse(); - return; - } + // Grab all the bids for each slot + for (var adSlotId in slotIdMap) { + var bidObj = slotIdMap[adSlotId]; + var adUnitCode = bidObj.placementCode; + + var bids = []; + + // Grab the bid for current slot + for (var cpmAndSlotId in indexObj) { + var match = /(T\d_)?(.+)_(\d+)_(\d+)/.exec(cpmAndSlotId); + var tier = match[1] || ''; + var slotID = match[2]; + var sizeID = match[3]; + var currentCPM = match[4]; + + var slotName = slotID + '_' + sizeID; + var slotObj = getSlotObj(cygnus_index_args, tier + slotName); - utils._each(indexObj, function (adContents, cpmAndSlotId) { - utils._each(slotIdMap, function (bid, adSlotId) { - var obj = cpmAndSlotId.split('_'); - var currentId = obj[0]; - var currentCPM = obj[1]; - if (currentId === adSlotId) { - var bidObj = slotIdMap[adSlotId]; - var adUnitCode = bidObj.placementCode; - var slotObj = getSlotObj(cygnus_index_args, adSlotId); - - bid = bidfactory.createBid(1); + // Bid is for the current slot + if (slotID === adSlotId) { + var bid = bidfactory.createBid(1); bid.cpm = currentCPM / 100; - bid.ad = adContents[0]; + bid.ad = indexObj[cpmAndSlotId][0]; bid.ad_id = adSlotId; bid.bidderCode = ADAPTER_CODE; bid.width = slotObj.width; bid.height = slotObj.height; bid.siteID = slotObj.siteID; - bidmanager.addBidResponse(adUnitCode, bid); + bids.push(bid); } - }); - }); + } + + var currentBid = undefined; + + //Pick the highest bidding price for this slot + if (bids.length > 0) { + // Choose the highest bid + for (var i = 0; i < bids.length; i++) { + var bid = bids[i]; + if (typeof currentBid === 'undefined') { + currentBid = bid; + continue; + } + + if (bid.cpm > currentBid.cpm) { + currentBid = bid; + } + } + + // No bids for expected bid, pass bid + } else { + var bid = bidfactory.createBid(2); + bid.bidderCode = ADAPTER_CODE; + currentBid = bid; + } + + bidmanager.addBidResponse(adUnitCode, currentBid); + } } catch (e) { utils.logError('Error calling index adapter', ADAPTER_NAME, e); logErrorBidResponse(); @@ -395,6 +447,19 @@ var IndexExchangeAdapter = function IndexExchangeAdapter() { }; } + /* + Function in order to add a slot into the list if it hasn't been created yet, else it returns the same list. + */ + function mergeSlotInto(slot,slotList){ + for(var i = 0; i < slotList.length; i++){ + if(slot.id === slotList[i].id){ + return slotList; + } + } + slotList.push(slot); + return slotList; + } + function getSlotObj(obj, id) { var arr = obj.slots; var returnObj = {}; From 0d69bb0cc5e3fbcf5e01c3440ab9062e01a4ec0d Mon Sep 17 00:00:00 2001 From: Steven Hadfield Date: Thu, 7 Apr 2016 11:38:04 -0700 Subject: [PATCH 087/160] Fix for AOL adapter not checking whether the page is requested via HTTPS #302 --- src/adapters/aol.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/adapters/aol.js b/src/adapters/aol.js index 5f13b35b626..fff201e39f6 100644 --- a/src/adapters/aol.js +++ b/src/adapters/aol.js @@ -128,7 +128,7 @@ var AolAdapter = function AolAdapter() { server: bid.params.server, // By default, DAC.js will use the US region endpoint (adserver.adtechus.com) sizeid: bid.params.sizeId || 0, pageid: bid.params.pageId, - secure: false, + secure: document.location.protocol === 'https:', serviceType: 'pubapi', performScreenDetection: false, alias: alias, From 5bcae1ac1a67b327d81df2eaa1aef115bd77eebd Mon Sep 17 00:00:00 2001 From: astudnicky Date: Fri, 8 Apr 2016 09:23:55 -0400 Subject: [PATCH 088/160] Add Sonobi Adapter --- integrationExamples/gpt/pbjs_example_gpt.html | 14 ++++ package.json | 1 + src/adapters/sonobi.js | 76 +++++++++++++++++++ 3 files changed, 91 insertions(+) create mode 100644 src/adapters/sonobi.js diff --git a/integrationExamples/gpt/pbjs_example_gpt.html b/integrationExamples/gpt/pbjs_example_gpt.html index c9e5edfa415..3168ecfb5c3 100644 --- a/integrationExamples/gpt/pbjs_example_gpt.html +++ b/integrationExamples/gpt/pbjs_example_gpt.html @@ -116,6 +116,20 @@ tagid: '123' // tag Id } }, + { + bidder: 'sonobi', // New format + params: { + dom_id: 'PER SLOT', // dom Id + ad_unit:'PER SLOT' // ad unit code + } + }, + { + bidder: 'sonobi', // Old account format + params: { + dom_id: 'PER SLOT', // dom Id + placement_id:'PER SLOT' // placement Id + } + }, { bidder: 'nginad', params: { diff --git a/package.json b/package.json index 9658f0aeaff..19fb012c5d7 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "pubmatic", "pulsepoint", "rubicon", + "sonobi", "sovrn", "springserve", "triplelift", diff --git a/src/adapters/sonobi.js b/src/adapters/sonobi.js new file mode 100644 index 00000000000..6dc3d9fb37f --- /dev/null +++ b/src/adapters/sonobi.js @@ -0,0 +1,76 @@ +var bidfactory = require('../bidfactory.js'); +var bidmanager = require('../bidmanager.js'); +var adloader = require('../adloader.js'); +var utils = require('../utils'); + +var SonobiAdapter = function SonobiAdapter(){ + var test = false; // tag tester = true || false + var cb_map = {}; + + function _phone_in(params){ + var trinity = 'https://apex.go.sonobi.com/trinity.js?key_maker='; + var bids = params.bids || []; + adloader.loadScript(trinity + JSON.stringify(_keymaker(bids)) + '&cv=' + _operator(), null); + } + + function _keymaker(bids){ // Make keys + var keyring = {}; + utils._each(bids, function(o){ + var sizes = []; + utils._each(o.sizes, function(size){ + sizes.push(size.join('x')); + }); + sizes = sizes.toString(); + if (!!o.params.ad_unit && o.params.ad_unit.length > 0) { + // Cypher + keyring[o.params.ad_unit + '|' + o.params.dom_id] = sizes; + cb_map[o.params.ad_unit + '|' + o.params.dom_id] = o.placementCode; + } else if (!!o.params.placement_id && o.params.placement_id.length > 0) { + // Morpheus + keyring[o.params.dom_id] = o.params.placement_id + (test ? '-test' : '') + '|' + sizes; + cb_map[o.params.dom_id] = o.placementCode; + } else { + utils.logError('sonobi unable to bid: Improper parameters for ' + o.placementCode); + } + }); + return keyring; + } + + function _operator(){ // Uniqify callbacks + var uniq = "cb" + utils.getUniqueIdentifierStr(); + window[uniq] = _trinity; + return uniq; + } + + function _trinity(response){ // Callback + var slots = response.slots || {}; + var sbi_dc = response.sbi_dc || ''; + var bidObject = {}; + for (var slot in slots) { + if (slots[slot].sbi_aid){ + bidObject = bidfactory.createBid(1); + bidObject.bidderCode = 'sonobi'; + bidObject.cpm = Number(slots[slot].sbi_mouse); + bidObject.ad = _get_creative(sbi_dc, slots[slot].sbi_aid); + bidObject.width = Number(slots[slot].sbi_size.split('x')[0]); + bidObject.height = Number(slots[slot].sbi_size.split('x')[1]); + bidmanager.addBidResponse(cb_map[slot], bidObject); + } else { // No aid? No ad. + bidObject = bidfactory.createBid(2); + bidObject.bidderCode = 'sonobi'; + bidmanager.addBidResponse(cb_map[slot], bidObject); + } + } + } + + function _get_creative(sbi_dc, sbi_aid){ + var creative = ''; + return creative; + } + + return { callBids: _phone_in }; +}; + +module.exports = SonobiAdapter; \ No newline at end of file From 421b5a5892be89f622eb221d3d4cf6227e83b33e Mon Sep 17 00:00:00 2001 From: protonate Date: Thu, 7 Apr 2016 10:43:09 -0700 Subject: [PATCH 089/160] Repo account and prebid version metadata Set a REPO_AND_VERSION constant during webpack that can be used to determine which Github repo the code was cloned from and the current version of Prebid. --- package.json | 1 + src/adapters/sovrn.js | 10 +--------- src/constants.json | 1 + webpack.conf.js | 22 +++++++++++++++++++++- 4 files changed, 24 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 9658f0aeaff..32f39ed36e8 100644 --- a/package.json +++ b/package.json @@ -93,6 +93,7 @@ "requirejs": "^2.1.20", "run-sequence": "^1.1.4", "sinon": "^1.12.1", + "string-replace-webpack-plugin": "0.0.3", "uglify-js": "^2.4.15", "webpack": "^1.12.3", "webpack-stream": "^3.1.0", diff --git a/src/adapters/sovrn.js b/src/adapters/sovrn.js index ce2bc6535c2..2e56e1fa439 100644 --- a/src/adapters/sovrn.js +++ b/src/adapters/sovrn.js @@ -70,19 +70,11 @@ var SovrnAdapter = function SovrnAdapter() { }; var scriptUrl = '//' + sovrnUrl + '?callback=window.pbjs.sovrnResponse' + - '&src=' + _getSource() + + '&src=' + CONSTANTS.REPO_AND_VERSION + '&br=' + encodeURIComponent(JSON.stringify(sovrnBidReq)); adloader.loadScript(scriptUrl, null); } - function _getSource() { - var packageJson = require('../../package.json'); - var repositoryUrlParts = packageJson.repository.url.split('/'); - var source = repositoryUrlParts.length > 3 && repositoryUrlParts[3] === 'sovrn' ? 'sovrn': 'oss'; // github account - - return source + '_prebid_' + packageJson.version; - } - function addBlankBidResponsesForAllPlacementsExceptThese(placementsWithBidsBack) { utils._each(allPlacementCodes, function (placementCode) { if (utils.contains(placementsWithBidsBack, placementCode)) { diff --git a/src/constants.json b/src/constants.json index 865c91d4694..c646737f9f5 100644 --- a/src/constants.json +++ b/src/constants.json @@ -9,6 +9,7 @@ "ADSERVER_TARGETING": "adserverTargeting", "BD_SETTING_STANDARD": "standard" }, + "REPO_AND_VERSION": "%%REPO_AND_VERSION%%", "DEBUG_MODE": "pbjs_debug", "STATUS": { "GOOD": 1, diff --git a/webpack.conf.js b/webpack.conf.js index 6dcc1cd9e98..999c33cd0cc 100644 --- a/webpack.conf.js +++ b/webpack.conf.js @@ -1,3 +1,6 @@ +var prebid = require('./package.json'); +var StringReplacePlugin = require('string-replace-webpack-plugin'); + module.exports = { output: { filename: 'prebid.js' @@ -27,7 +30,24 @@ module.exports = { test: /adaptermanager.js/, include: /(src)/, loader: 'adapterLoader' + }, + { + test: /constants.json$/, + include: /(src)/, + loader: StringReplacePlugin.replace({ + replacements: [ + { + pattern: /%%REPO_AND_VERSION%%/g, + replacement: function (match, p1, offset, string) { + return `${prebid.repository.url.split('/')[3]}_prebid_${prebid.version}`; + } + } + ] + }) } ] - } + }, + plugins: [ + new StringReplacePlugin() + ] }; From 3551d9ed578c3c6b9750c2c4e58f0f5d879c1324 Mon Sep 17 00:00:00 2001 From: Bruno Bornsztein Date: Thu, 31 Mar 2016 13:51:12 -0500 Subject: [PATCH 090/160] Make GA integration work with named trackers allow configuring ga tracker name and add some tests add ga spec coding style updates update prefixedSend usage use trackerSend instead of prefixedSend fix tests fix trackerSend undefined error - @protonate --- src/ga.js | 21 ++++++++++++++------- test/spec/ga_spec.js | 28 ++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 7 deletions(-) create mode 100644 test/spec/ga_spec.js diff --git a/src/ga.js b/src/ga.js index c1e9daefe30..f7918f82cfb 100644 --- a/src/ga.js +++ b/src/ga.js @@ -19,6 +19,7 @@ var _category = 'Prebid.js Bids'; var _eventCount = 0; var _enableDistribution = false; var _timedOutBidders = []; +var _trackerSend = null; /** * This will enable sending data to google analytics. Only call once, or duplicate data will be sent! @@ -33,6 +34,8 @@ exports.enableAnalytics = function (gaOptions) { _gaGlobal = 'ga'; } + _trackerSend = gaOptions.trackerName ? gaOptions.trackerName + '.send' : 'send'; + if (typeof gaOptions.enableDistribution !== 'undefined') { _enableDistribution = gaOptions.enableDistribution; } @@ -88,6 +91,10 @@ exports.enableAnalytics = function (gaOptions) { }); }; +exports.getTrackerSend = function getTrackerSend() { + return _trackerSend; +}; + /** * Check if gaGlobal or window.ga is defined on page. If defined execute all the GA commands */ @@ -178,7 +185,7 @@ function sendBidRequestToGa(bid) { if (bid && bid.bidderCode) { _analyticsQueue.push(function () { _eventCount++; - window[_gaGlobal]('send', 'event', _category, 'Requests', bid.bidderCode, 1, _disibleInteraction); + window[_gaGlobal](_trackerSend, 'event', _category, 'Requests', bid.bidderCode, 1, _disibleInteraction); }); } @@ -195,7 +202,7 @@ function sendBidResponseToGa(bid) { if (typeof bid.timeToRespond !== 'undefined' && _enableDistribution) { _eventCount++; var dis = getLoadTimeDistribution(bid.timeToRespond); - window[_gaGlobal]('send', 'event', 'Prebid.js Load Time Distribution', dis, bidder, 1, _disibleInteraction); + window[_gaGlobal](_trackerSend, 'event', 'Prebid.js Load Time Distribution', dis, bidder, 1, _disibleInteraction); } if (bid.cpm > 0) { @@ -203,11 +210,11 @@ function sendBidResponseToGa(bid) { var cpmDis = getCpmDistribution(bid.cpm); if (_enableDistribution) { _eventCount++; - window[_gaGlobal]('send', 'event', 'Prebid.js CPM Distribution', cpmDis, bidder, 1, _disibleInteraction); + window[_gaGlobal](_trackerSend, 'event', 'Prebid.js CPM Distribution', cpmDis, bidder, 1, _disibleInteraction); } - window[_gaGlobal]('send', 'event', _category, 'Bids', bidder, cpmCents, _disibleInteraction); - window[_gaGlobal]('send', 'event', _category, 'Bid Load Time', bidder, bid.timeToRespond, _disibleInteraction); + window[_gaGlobal](_trackerSend, 'event', _category, 'Bids', bidder, cpmCents, _disibleInteraction); + window[_gaGlobal](_trackerSend, 'event', _category, 'Bid Load Time', bidder, bid.timeToRespond, _disibleInteraction); } }); } @@ -223,7 +230,7 @@ function sendBidTimeouts(bid) { utils._each(_timedOutBidders, function (bidderCode) { if (bid.bidder === bidderCode) { _eventCount++; - window[_gaGlobal]('send', 'event', _category, 'Timeouts', bidderCode, bid.timeToRespond, _disibleInteraction); + window[_gaGlobal](_trackerSend, 'event', _category, 'Timeouts', bidderCode, bid.timeToRespond, _disibleInteraction); } }); }); @@ -236,7 +243,7 @@ function sendBidWonToGa(bid) { var cpmCents = convertToCents(bid.cpm); _analyticsQueue.push(function () { _eventCount++; - window[_gaGlobal]('send', 'event', _category, 'Wins', bid.bidderCode, cpmCents, _disibleInteraction); + window[_gaGlobal](_trackerSend, 'event', _category, 'Wins', bid.bidderCode, cpmCents, _disibleInteraction); }); checkAnalytics(); diff --git a/test/spec/ga_spec.js b/test/spec/ga_spec.js new file mode 100644 index 00000000000..caef44790bc --- /dev/null +++ b/test/spec/ga_spec.js @@ -0,0 +1,28 @@ +var assert = require('assert'); +var ga = require('../../src/ga'); + +describe('Ga', function () { + + describe('enableAnalytics', function () { + + it('should accept a tracker name option and output prefixed send string', function () { + var options = { trackerName: 'foo' }; + ga.enableAnalytics(options); + + var output = ga.getTrackerSend(); + assert.equal(output, 'foo.send'); + }); + + + it('should output normal send string when trackerName is not set', function () { + var options = {}; + ga.enableAnalytics(options); + + var output = ga.getTrackerSend(); + assert.equal(output, 'send'); + }); + + }); + + +}); From 73918b56c0d4ee39897e95dd09b7aea77d2d4a20 Mon Sep 17 00:00:00 2001 From: protonate Date: Mon, 11 Apr 2016 18:02:24 -0700 Subject: [PATCH 091/160] support for adapers with aliases in adapterLoader - updated to simplify code, add documentation - updated to call exports.aliasBidAdapter in adaptermanager - updated to alter config sytntax, simplify and document note: `npm install` to pick up [blockLoader]() package. --- loaders/adapterLoader.js | 127 +++++++++++++++++++++++++++++---------- package.json | 4 +- src/adaptermanager.js | 7 +-- 3 files changed, 99 insertions(+), 39 deletions(-) diff --git a/loaders/adapterLoader.js b/loaders/adapterLoader.js index a9e4301c14b..c096cc55d0b 100644 --- a/loaders/adapterLoader.js +++ b/loaders/adapterLoader.js @@ -1,56 +1,117 @@ +/** adapterLoader + * Webpack loader to insert dynamic javascript into `./src/adaptermanager.js` + * This is used in `./webpack.conf.js` + * */ + 'use strict'; const fs = require('fs'); const blockLoader = require('block-loader'); -let adapters = require('../package.json').adapters; +const adapters = require('../package.json').adapters; + +const files = fs.readdirSync('src/adapters').map((file) => file.replace(/\.[^/.]+$/, '')); +const adapterNames = adapters.map(getNames).filter(getUniques); +const aliases = adapters.filter(getAliases); var options = { start: '/** INSERT ADAPTERS - DO NOT EDIT OR REMOVE */', end: '/** END INSERT ADAPTERS */', - process: function insertAdapters() { - const files = fs.readdirSync('src/adapters').map((file) => file.replace(/\.[^/.]+$/, '')); + process: insertAdapters +}; - if (!adapters || !adapters.length) { - console.log('Prebid Warning: adapters config not found in package.json, building with all' + - ' adapters'); - adapters = files; - } +/** + * Returns a block of javascript statements to load adapter modules, register the adapters and + * set adapter aliases + * @returns {*} + */ +function insertAdapters() { - let inserts = adapters.filter((adapter) => { - if (files.includes(adapter)) { - return adapter; - } else { - console.log(`Prebid Warning: no adapter found for ${adapter}, continuing.`); - } - }); - - if (!inserts.length) { - console.log('Prebid Warning: no matching adapters found for config, building with all' + - ' adapters.'); + if (!adapters) { + console.log('Prebid Warning: adapters config not found in package.json, no adapters will' + + ' be loaded'); + return ''; + } + + const inserts = adapterNames.map(name => { + if (files.includes(name)) { + return name; + } else { + console.log(`Prebid Warning: no adapter found for ${name}, continuing.`); } + }); - inserts = inserts.length ? inserts : files; - return inserts.map((adapter) => { - if (adapter === 'appnexusAst') { - return `import { AppnexusAst } from './adapters/appnexusAst';\n` + - `exports.registerBidAdapter(new AppnexusAst('appnexus'), 'appnexus');\n`; - } - - return `var ${adapterName(adapter)} = require('./adapters/${adapter}.js');\n` + - `exports.registerBidAdapter(new ${adapterName(adapter)}` + - `${useCreateNew(adapter)}(), '${adapter}');\n`; - }).join(''); + if (!inserts.length) { + console.log('Prebid Warning: no matching adapters found for config, no adapters will be' + + ' loaded.'); + return ''; } -}; + return inserts.map(name => { + if (name === 'appnexusAst') { + return `import { AppnexusAst } from './adapters/appnexusAst'; + exports.registerBidAdapter(new AppnexusAst('appnexus'), 'appnexus');\n`; + } else { + return `var ${adapterName(name)} = require('./adapters/${name}.js'); + exports.registerBidAdapter(new ${adapterName(name)}${useCreateNew(name)}(), '${name}');\n`; + } + }) + .concat(aliases.map(adapter => { + const name = Object.keys(adapter)[0]; + return `exports.aliasBidAdapter('${name}','${adapter[name].alias}');\n`; + })) + .join(''); +} + +/** + * Derive the variable name to use for the adapter + * @param adapter + * @returns {string} + */ function adapterName(adapter) { - let result = adapter.split(''); + const result = adapter.split(''); return result[0].toUpperCase() + result.join('').substr(1) + 'Adapter'; } -// some adapters export an object with a `createNew` constructor so accommodate this pattern +/** + * Some adapters export an object with a `createNew` constructor so accommodate this pattern + * @param adapter + * @returns {string} + */ function useCreateNew(adapter) { return adapter === 'appnexus' ? '.createNew' : ''; } +/** + * Filter an array to return unique values + * @param value current array element value + * @param index current array element index + * @param self current array + * @returns {boolean} if true the current array element is returned + * + * http://stackoverflow.com/questions/1960473/unique-values-in-an-array + */ +function getUniques(value, index, self) { + return self.indexOf(value) === index; +} + +/** + * Filter to derive the adapter name from array elements as strings or objects + * @param adapter + * @returns {*} + */ +function getNames(adapter) { + // if `length` then `adapter` is a string, otherwise an object + return adapter.length ? adapter : Object.keys(adapter)[0]; +} + +/** + * Return adapter objects that have an alias field + * @param adapter + * @returns {*} + */ +function getAliases(adapter) { + const name = Object.keys(adapter)[0]; + return adapter && name && adapter[name].alias; +} + module.exports = blockLoader(options); diff --git a/package.json b/package.json index a2d43bee709..49b7488bb6d 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,9 @@ "triplelift", "yieldbot", "nginad", - "brightcom" + "brightcom", + { "appnexus": {"alias": "brealtime"} }, + { "appnexus": {"alias": "pagescience"} } ], "author": "the prebid.js contributors", "license": "Apache-2.0", diff --git a/src/adaptermanager.js b/src/adaptermanager.js index 1e32c743072..5bd70733ea5 100644 --- a/src/adaptermanager.js +++ b/src/adaptermanager.js @@ -6,7 +6,6 @@ var CONSTANTS = require('./constants.json'); var events = require('./events'); import { BaseAdapter } from './adapters/baseAdapter'; - var _bidderRegistry = {}; exports.bidderRegistry = _bidderRegistry; @@ -66,7 +65,7 @@ exports.aliasBidAdapter = function (bidderCode, alias) { } else { try { let newAdapter = null; - if(bidAdaptor instanceof BaseAdapter) { + if (bidAdaptor instanceof BaseAdapter) { //newAdapter = new bidAdaptor.constructor(alias); utils.logError(bidderCode + ' bidder does not currently support aliasing.', 'adaptermanager.aliasBidAdapter'); } else { @@ -84,8 +83,6 @@ exports.aliasBidAdapter = function (bidderCode, alias) { }; /** INSERT ADAPTERS - DO NOT EDIT OR REMOVE */ + // here be adapters /** END INSERT ADAPTERS */ - -//default bidder alias -exports.aliasBidAdapter('appnexus', 'brealtime'); From 4292732e2c3b96e2b52ce53dab7f633fc9b1e3e8 Mon Sep 17 00:00:00 2001 From: Michael Miller Date: Thu, 14 Apr 2016 15:31:09 -0400 Subject: [PATCH 092/160] Remove unneeded variable --- src/prebid.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/prebid.js b/src/prebid.js index 8e8fee24d36..57ed01ebcb5 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -325,8 +325,7 @@ function resetBids() { pb_bidsTimedOut = false; } -function requestAllBids(tmout) { - var timeout = tmout; +function requestAllBids(timeout) { resetBids(); init(timeout); } From 96f2ac0242f3c3633e3b025da0723878c461b3e5 Mon Sep 17 00:00:00 2001 From: protonate Date: Thu, 14 Apr 2016 13:23:51 -0700 Subject: [PATCH 093/160] add `setPriceGranularity` API method updated to set medium price bucket granularity as default. updated to cap at string "20.00". --- src/bidmanager.js | 26 +- src/constants.json | 6 + src/prebid.js | 8 + src/utils.js | 26 +- test/spec/bidmanager_spec.js | 623 +++++++++++++++++++---------------- 5 files changed, 392 insertions(+), 297 deletions(-) diff --git a/src/bidmanager.js b/src/bidmanager.js index a01462469a0..c1eca1cb5b4 100644 --- a/src/bidmanager.js +++ b/src/bidmanager.js @@ -24,11 +24,9 @@ var bidResponseReceivedCount = {}; exports.bidResponseReceivedCount = bidResponseReceivedCount; var expectedBidsCount = {}; - var _allBidsAvailable = false; - var _callbackExecuted = false; - +var _granularity = CONSTANTS.GRANULARITY_OPTIONS.MEDIUM; var defaultBidderSettingsMap = {}; var bidderStartTimes = {}; @@ -151,6 +149,7 @@ exports.addBidResponse = function (adUnitCode, bid) { bid.pbLg = priceStringsObj.low; bid.pbMg = priceStringsObj.med; bid.pbHg = priceStringsObj.high; + bid.pbAg = priceStringsObj.auto; //put adUnitCode into bid bid.adUnitCode = adUnitCode; @@ -226,7 +225,15 @@ exports.getKeyValueTargetingPairs = function (bidderCode, custBidObj) { }, { key: 'hb_pb', val: function (bidResponse) { - return bidResponse.pbMg; + if (_granularity === CONSTANTS.GRANULARITY_OPTIONS.AUTO) { + return bidResponse.pbAg; + } else if (_granularity === CONSTANTS.GRANULARITY_OPTIONS.LOW) { + return bidResponse.pbLg; + } else if (_granularity === CONSTANTS.GRANULARITY_OPTIONS.MEDIUM) { + return bidResponse.pbMg; + } else if (_granularity === CONSTANTS.GRANULARITY_OPTIONS.HIGH) { + return bidResponse.pbHg; + } } }, { key: 'hb_size', @@ -283,6 +290,17 @@ function setKeys(keyValues, bidderSettings, custBidObj) { return keyValues; } +exports.setPriceGranularity = function setPriceGranularity(granularity) { + var granularityOptions = CONSTANTS.GRANULARITY_OPTIONS; + if (Object.keys(granularityOptions).filter(option => granularity === granularityOptions[option])) { + _granularity = granularity; + } else { + utils.logWarn('Prebid Warning: setPriceGranularity was called with invalid setting, using' + + ' `medium` as default.'); + _granularity = CONSTANTS.GRANULARITY_OPTIONS.MEDIUM; + } +}; + exports.registerDefaultBidderSetting = function (bidderCode, defaultSetting) { defaultBidderSettingsMap[bidderCode] = defaultSetting; }; diff --git a/src/constants.json b/src/constants.json index c646737f9f5..72aab6ffdb3 100644 --- a/src/constants.json +++ b/src/constants.json @@ -36,5 +36,11 @@ }, "EVENT_ID_PATHS": { "bidWon": "adUnitCode" + }, + "GRANULARITY_OPTIONS": { + "LOW": "low", + "MEDIUM": "medium", + "HIGH": "high", + "AUTO": "auto" } } diff --git a/src/prebid.js b/src/prebid.js index 8e8fee24d36..2bb1fb20977 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -959,4 +959,12 @@ pbjs.aliasBidder = function (bidderCode, alias) { } }; +pbjs.setPriceGranularity = function (granularity) { + if (!granularity) { + utils.logError('Prebid Error: no value passed to `setPriceGranularity()`'); + } else { + bidmanager.setPriceGranularity(granularity); + } +}; + processQue(); diff --git a/src/utils.js b/src/utils.js index 608f4e706e4..a082d19e39a 100644 --- a/src/utils.js +++ b/src/utils.js @@ -267,35 +267,53 @@ exports.getPriceBucketString = function (cpm) { var low = ''; var med = ''; var high = ''; + var auto = ''; + var cpmFloat = 0; var returnObj = { low: low, med: med, - high: high + high: high, + auto: auto }; try { cpmFloat = parseFloat(cpm); if (cpmFloat) { - //round to closet .5 + //round to closest .5 if (cpmFloat > _lgPriceCap) { returnObj.low = _lgPriceCap.toFixed(2); } else { returnObj.low = (Math.floor(cpm * 2) / 2).toFixed(2); } - //round to closet .1 + //round to closest .1 if (cpmFloat > _mgPriceCap) { returnObj.med = _mgPriceCap.toFixed(2); } else { returnObj.med = (Math.floor(cpm * 10) / 10).toFixed(2); } - //round to closet .01 + //round to closest .01 if (cpmFloat > _hgPriceCap) { returnObj.high = _hgPriceCap.toFixed(2); } else { returnObj.high = (Math.floor(cpm * 100) / 100).toFixed(2); } + + // round auto default sliding scale + if (cpmFloat <= 5) { + // round to closest .05 + returnObj.auto = (Math.floor(cpm * 20) / 20).toFixed(2); + } else if (cpmFloat <= 10) { + // round to closest .10 + returnObj.auto = (Math.floor(cpm * 10) / 10).toFixed(2); + } else if (cpmFloat <= 20) { + // round to closest .50 + returnObj.auto = (Math.floor(cpm * 2) / 2).toFixed(2); + } else { + // cap at 20.00 + returnObj.auto = '20.00'; + } } } catch (e) { this.logError('Exception parsing CPM :' + e.message); diff --git a/test/spec/bidmanager_spec.js b/test/spec/bidmanager_spec.js index ec7608971d0..1c4dc45f39a 100644 --- a/test/spec/bidmanager_spec.js +++ b/test/spec/bidmanager_spec.js @@ -6,300 +6,345 @@ var assert = require("assert"); var utils = require('../../src/utils'); var bidmanager = require('../../src/bidmanager'); - describe('replaceTokenInString', function(){ +describe('replaceTokenInString', function () { + + it('should replace all given tokens in a String', function () { + var tokensToReplace = { + 'foo': 'bar', + 'zap': 'quux' + }; + + var output = utils.replaceTokenInString("hello %FOO%, I am %ZAP%", tokensToReplace, "%"); + assert.equal(output, "hello bar, I am quux"); + }); + + it('should ignore tokens it does not see', function () { + var output = utils.replaceTokenInString("hello %FOO%", {}, "%"); + + assert.equal(output, "hello %FOO%"); + }); +}); + +describe('bidmanager.js', function () { + + describe('getKeyValueTargetingPairs', function () { + var bid = {}; + var bidPriceCpm = 5.578; + var bidPbLg = 5.50; + var bidPbMg = 5.50; + var bidPbHg = 5.57; + var bidPbAg = 5.50; + + var adUnitCode = '12345'; + var bidderCode = 'appnexus'; + var size = '300x250'; + var adId = '1adId'; + + before(function () { + bid.cpm = bidPriceCpm; + bid.pbLg = bidPbLg; + bid.pbMg = bidPbMg; + bid.pbHg = bidPbHg; + bid.pbAg = bidPbAg; + + bid.height = 300; + bid.width = 250; + bid.adUnitCode = adUnitCode; + bid.getSize = function () { + return this.height + 'x' + this.width; + }; + bid.bidderCode = bidderCode; + bid.adId = adId; - it('should replace all given tokens in a String', function() { - var tokensToReplace = { - 'foo': 'bar', - 'zap': 'quux' - }; + }); + + it('No bidder level configuration defined - default', function () { + var expected = { + "hb_bidder": bidderCode, + "hb_adid": adId, + "hb_pb": bidPbMg, + "hb_size": size + }; + var response = bidmanager.getKeyValueTargetingPairs(bidderCode, bid); + assert.deepEqual(response, expected); + + }); + + it('Custom configuration for all bidders', function () { + pbjs.bidderSettings = + { + standard: { + adserverTargeting: [ + { + key: "hb_bidder", + val: function (bidResponse) { + return bidResponse.bidderCode; + } + }, { + key: "hb_adid", + val: function (bidResponse) { + return bidResponse.adId; + } + }, { + key: "hb_pb", + val: function (bidResponse) { + //change default here + return bidResponse.pbHg; + } + }, { + key: "hb_size", + val: function (bidResponse) { + return bidResponse.size; + + } + } + ] + + } + }; + + var expected = { + "hb_bidder": bidderCode, + "hb_adid": adId, + "hb_pb": bidPbHg, + "hb_size": size + }; + var response = bidmanager.getKeyValueTargetingPairs(bidderCode, bid); + assert.deepEqual(response, expected); + + }); + + it('Custom configuration for one bidder', function () { + pbjs.bidderSettings = + { + appnexus: { + adserverTargeting: [ + { + key: "hb_bidder", + val: function (bidResponse) { + return bidResponse.bidderCode; + } + }, { + key: "hb_adid", + val: function (bidResponse) { + return bidResponse.adId; + } + }, { + key: "hb_pb", + val: function (bidResponse) { + //change default here + return bidResponse.pbHg; + } + }, { + key: "hb_size", + val: function (bidResponse) { + return bidResponse.size; + + } + } + ] + + } + }; + + var expected = { + "hb_bidder": bidderCode, + "hb_adid": adId, + "hb_pb": bidPbHg, + "hb_size": size + }; + var response = bidmanager.getKeyValueTargetingPairs(bidderCode, bid); + assert.deepEqual(response, expected); - var output = utils.replaceTokenInString("hello %FOO%, I am %ZAP%", tokensToReplace, "%"); - assert.equal(output, "hello bar, I am quux"); - }); + }); + + it('Custom configuration for one bidder - not matched', function () { + pbjs.bidderSettings = + { + nonExistentBidder: { + adserverTargeting: [ + { + key: "hb_bidder", + val: function (bidResponse) { + return bidResponse.bidderCode; + } + }, { + key: "hb_adid", + val: function (bidResponse) { + return bidResponse.adId; + } + }, { + key: "hb_pb", + val: function (bidResponse) { + //change default here + return bidResponse.pbHg; + } + }, { + key: "hb_size", + val: function (bidResponse) { + return bidResponse.size; + + } + } + ] + + } + }; + + var expected = { + "hb_bidder": bidderCode, + "hb_adid": adId, + "hb_pb": bidPbMg, + "hb_size": size + }; + var response = bidmanager.getKeyValueTargetingPairs(bidderCode, bid); + assert.deepEqual(response, expected); + + }); - it('should ignore tokens it does not see', function() { - var output = utils.replaceTokenInString("hello %FOO%", {}, "%"); + it('Custom bidCpmAdjustment for one bidder and inherit standard', function () { + pbjs.bidderSettings = + { + appnexus: { + bidCpmAdjustment: function (bidCpm) { + return bidCpm * 0.7; + }, + }, + standard: { + adserverTargeting: [ + { + key: "hb_bidder", + val: function (bidResponse) { + return bidResponse.bidderCode; + } + }, { + key: "hb_adid", + val: function (bidResponse) { + return bidResponse.adId; + } + }, { + key: "hb_pb", + val: function (bidResponse) { + //change default here + return 10.00; + } + } + ] + + } + }; + + var expected = { "hb_bidder": bidderCode, "hb_adid": adId, "hb_pb": 10.0 }; + var response = bidmanager.getKeyValueTargetingPairs(bidderCode, bid); + assert.deepEqual(response, expected); - assert.equal(output, "hello %FOO%"); - }); }); + it('Custom bidCpmAdjustment AND custom configuration for one bidder and inherit standard settings', function () { + pbjs.bidderSettings = + { + appnexus: { + bidCpmAdjustment: function (bidCpm) { + return bidCpm * 0.7; + }, + adserverTargeting: [ + { + key: "hb_bidder", + val: function (bidResponse) { + return bidResponse.bidderCode; + } + }, { + key: "hb_adid", + val: function (bidResponse) { + return bidResponse.adId; + } + }, { + key: "hb_pb", + val: function (bidResponse) { + //change default here + return 15.00; + } + } + ] + }, + standard: { + adserverTargeting: [ + { + key: "hb_bidder", + val: function (bidResponse) { + return bidResponse.bidderCode; + } + }, { + key: "hb_adid", + val: function (bidResponse) { + return bidResponse.adId; + } + }, { + key: "hb_pb", + val: function (bidResponse) { + //change default here + return 10.00; + }, + }, + { + key: "hb_size", + val: function (bidResponse) { + return bidResponse.size; + + } + } + ] + + } + }; + + var expected = { + "hb_bidder": bidderCode, + "hb_adid": adId, + "hb_pb": 15.0, + "hb_size": "300x250" + }; + var response = bidmanager.getKeyValueTargetingPairs(bidderCode, bid); + assert.deepEqual(response, expected); - describe('bidmanager.js', function(){ - - describe('getKeyValueTargetingPairs', function(){ - var bid = {}; - var bidPriceCpm = 5.578; - var bidPbLg = 5.50; - var bidPbMg = 5.50; - var bidPbHg = 5.57; - var adUnitCode = '12345'; - var bidderCode = 'appnexus'; - var size = '300x250'; - var adId = '1adId'; - - before(function() { - bid.cpm = bidPriceCpm; - bid.pbLg = bidPbLg; - bid.pbMg = bidPbMg; - bid.pbHg = bidPbHg; - bid.height = 300; - bid.width = 250; - bid.adUnitCode = adUnitCode; - bid.getSize = function(){ - return this.height + 'x' + this.width; - }; - bid.bidderCode = bidderCode; - bid.adId = adId; - - }); - - - it('No bidder level configuration defined - default', function() { - var expected = {"hb_bidder": bidderCode, "hb_adid": adId,"hb_pb": bidPbMg,"hb_size": size}; - var response = bidmanager.getKeyValueTargetingPairs(bidderCode, bid); - assert.deepEqual(response, expected); - - }); - - it('Custom configuration for all bidders', function() { - pbjs.bidderSettings = - { - standard: { - adserverTargeting: [{ - key: "hb_bidder", - val: function(bidResponse) { - return bidResponse.bidderCode; - } - }, { - key: "hb_adid", - val: function(bidResponse) { - return bidResponse.adId; - } - }, { - key: "hb_pb", - val: function(bidResponse) { - //change default here - return bidResponse.pbHg; - } - }, { - key: "hb_size", - val: function(bidResponse) { - return bidResponse.size; - - } - }] - - } - }; - - var expected = {"hb_bidder": bidderCode, "hb_adid": adId,"hb_pb": bidPbHg,"hb_size": size}; - var response = bidmanager.getKeyValueTargetingPairs(bidderCode, bid); - assert.deepEqual(response, expected); - - }); - - it('Custom configuration for one bidder', function() { - pbjs.bidderSettings = - { - appnexus: { - adserverTargeting: [{ - key: "hb_bidder", - val: function(bidResponse) { - return bidResponse.bidderCode; - } - }, { - key: "hb_adid", - val: function(bidResponse) { - return bidResponse.adId; - } - }, { - key: "hb_pb", - val: function(bidResponse) { - //change default here - return bidResponse.pbHg; - } - }, { - key: "hb_size", - val: function(bidResponse) { - return bidResponse.size; - - } - }] - - } - }; - - var expected = {"hb_bidder": bidderCode, "hb_adid": adId,"hb_pb": bidPbHg,"hb_size": size}; - var response = bidmanager.getKeyValueTargetingPairs(bidderCode, bid); - assert.deepEqual(response, expected); - - }); - - it('Custom configuration for one bidder - not matched', function() { - pbjs.bidderSettings = - { - nonExistentBidder: { - adserverTargeting: [{ - key: "hb_bidder", - val: function(bidResponse) { - return bidResponse.bidderCode; - } - }, { - key: "hb_adid", - val: function(bidResponse) { - return bidResponse.adId; - } - }, { - key: "hb_pb", - val: function(bidResponse) { - //change default here - return bidResponse.pbHg; - } - }, { - key: "hb_size", - val: function(bidResponse) { - return bidResponse.size; - - } - }] - - } - }; - - var expected = {"hb_bidder": bidderCode, "hb_adid": adId,"hb_pb": bidPbMg,"hb_size": size}; - var response = bidmanager.getKeyValueTargetingPairs(bidderCode, bid); - assert.deepEqual(response, expected); - - }); - - it('Custom bidCpmAdjustment for one bidder and inherit standard', function() { - pbjs.bidderSettings = - { - appnexus: { - bidCpmAdjustment : function(bidCpm){ - return bidCpm * 0.7; - }, - }, - standard: { - adserverTargeting: [{ - key: "hb_bidder", - val: function(bidResponse) { - return bidResponse.bidderCode; - } - }, { - key: "hb_adid", - val: function(bidResponse) { - return bidResponse.adId; - } - }, { - key: "hb_pb", - val: function(bidResponse) { - //change default here - return 10.00; - } - }] - - } - }; - - var expected = {"hb_bidder": bidderCode, "hb_adid": adId,"hb_pb": 10.0 }; - var response = bidmanager.getKeyValueTargetingPairs(bidderCode, bid); - assert.deepEqual(response, expected); - - }); - - it('Custom bidCpmAdjustment AND custom configuration for one bidder and inherit standard settings', function() { - pbjs.bidderSettings = - { - appnexus: { - bidCpmAdjustment : function(bidCpm){ - return bidCpm * 0.7; - }, - adserverTargeting: [{ - key: "hb_bidder", - val: function(bidResponse) { - return bidResponse.bidderCode; - } - }, { - key: "hb_adid", - val: function(bidResponse) { - return bidResponse.adId; - } - }, { - key: "hb_pb", - val: function(bidResponse) { - //change default here - return 15.00; - } - }] - }, - standard: { - adserverTargeting: [{ - key: "hb_bidder", - val: function(bidResponse) { - return bidResponse.bidderCode; - } - }, { - key: "hb_adid", - val: function(bidResponse) { - return bidResponse.adId; - } - }, { - key: "hb_pb", - val: function(bidResponse) { - //change default here - return 10.00; - }, - }, - { - key: "hb_size", - val: function(bidResponse) { - return bidResponse.size; - - } - }] - - } - }; - - var expected = {"hb_bidder": bidderCode, "hb_adid": adId,"hb_pb": 15.0, "hb_size":"300x250" }; - var response = bidmanager.getKeyValueTargetingPairs(bidderCode, bid); - assert.deepEqual(response, expected); - - }); - - - it('alwaysUseBid=true and inherit custom', function() { - pbjs.bidderSettings = - { - appnexus: { - alwaysUseBid : true, - adserverTargeting: [{ - key: "hb_bidder", - val: function(bidResponse) { - return bidResponse.bidderCode; - } - }, { - key: "hb_adid", - val: function(bidResponse) { - return bidResponse.adId; - } - }, { - key: "hb_pb", - val: function(bidResponse) { - return bidResponse.pbHg; - } - }] - } - }; - - var expected = {"hb_bidder": bidderCode, "hb_adid": adId,"hb_pb": 5.57, "hb_size":"300x250" }; - var response = bidmanager.getKeyValueTargetingPairs(bidderCode, bid); - assert.deepEqual(response, expected); - - }); - - }); }); + + it('alwaysUseBid=true and inherit custom', function () { + pbjs.bidderSettings = + { + appnexus: { + alwaysUseBid: true, + adserverTargeting: [ + { + key: "hb_bidder", + val: function (bidResponse) { + return bidResponse.bidderCode; + } + }, { + key: "hb_adid", + val: function (bidResponse) { + return bidResponse.adId; + } + }, { + key: "hb_pb", + val: function (bidResponse) { + return bidResponse.pbHg; + } + } + ] + } + }; + + var expected = { + "hb_bidder": bidderCode, + "hb_adid": adId, + "hb_pb": 5.57, + "hb_size": "300x250" + }; + var response = bidmanager.getKeyValueTargetingPairs(bidderCode, bid); + assert.deepEqual(response, expected); + + }); + + }); +}); From 8f4b6123b74769181a39febf1c6f693f8409c877 Mon Sep 17 00:00:00 2001 From: protonate Date: Fri, 15 Apr 2016 15:08:53 -0700 Subject: [PATCH 094/160] restore aliasBidAdapter call --- src/adaptermanager.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/adaptermanager.js b/src/adaptermanager.js index 5bd70733ea5..16f3ddd41ec 100644 --- a/src/adaptermanager.js +++ b/src/adaptermanager.js @@ -86,3 +86,6 @@ exports.aliasBidAdapter = function (bidderCode, alias) { // here be adapters /** END INSERT ADAPTERS */ + +//default bidder alias +exports.aliasBidAdapter('appnexus', 'brealtime'); From ba2e700f2d825a29e1cc2ec40589ac8cff523d37 Mon Sep 17 00:00:00 2001 From: protonate Date: Fri, 15 Apr 2016 18:23:19 -0700 Subject: [PATCH 095/160] Prebid.js 0.8.0 release --- package.json | 2 +- src/adaptermanager.js | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/package.json b/package.json index 49b7488bb6d..c30e13c5624 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "0.7.0", + "version": "0.8.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { diff --git a/src/adaptermanager.js b/src/adaptermanager.js index 16f3ddd41ec..5bd70733ea5 100644 --- a/src/adaptermanager.js +++ b/src/adaptermanager.js @@ -86,6 +86,3 @@ exports.aliasBidAdapter = function (bidderCode, alias) { // here be adapters /** END INSERT ADAPTERS */ - -//default bidder alias -exports.aliasBidAdapter('appnexus', 'brealtime'); From ba488d54322ab6d92b6b19fdadcbbde79086aab8 Mon Sep 17 00:00:00 2001 From: "charles.black" Date: Tue, 19 Apr 2016 11:02:44 -0400 Subject: [PATCH 096/160] Allow more than 20 slots to be requested to the ad server --- src/adapters/indexExchange.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/adapters/indexExchange.js b/src/adapters/indexExchange.js index b66063071aa..67f5851ff07 100644 --- a/src/adapters/indexExchange.js +++ b/src/adapters/indexExchange.js @@ -357,8 +357,7 @@ var IndexExchangeAdapter = function IndexExchangeAdapter() { } if (cygnus_index_args.slots.length > 20) { - utils.logError('Error calling index adapter, too many unique slot-sizes. Only 20 supported.', ADAPTER_NAME); - return; + utils.logError('Too many unique sizes on slots, will use the first 20.', ADAPTER_NAME); } bidmanager.setExpectedBidsCount(ADAPTER_CODE, expectedBids); From c14bd2e99081ce121bb638219911764f9edf42bc Mon Sep 17 00:00:00 2001 From: Steven Hadfield Date: Wed, 20 Apr 2016 12:39:49 -0700 Subject: [PATCH 097/160] Use package.json to populate version information (#325) * Use package.json to populate version information --- gulpfile.js | 5 ++++- src/prebid.js | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index 02318079992..f8c58f14340 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -18,12 +18,13 @@ var concat = require('gulp-concat'); var jscs = require('gulp-jscs'); var header = require('gulp-header'); var zip = require('gulp-zip'); +var replace = require('gulp-replace'); var CI_MODE = process.env.NODE_ENV === 'ci'; var prebid = require('./package.json'); var dateString = 'Updated : ' + (new Date()).toISOString().substring(0, 10); var packageNameVersion = prebid.name + '_' + prebid.version; -var banner = '/* <%= prebid.name %> v<%= prebid.version %> \n' + dateString + ' */\n'; +var banner = '/* <%= prebid.name %> v<%= prebid.version %>\n' + dateString + ' */\n'; // Tasks gulp.task('default', ['clean', 'quality', 'webpack']); @@ -43,6 +44,7 @@ gulp.task('devpack', function () { webpackConfig.devtool = 'source-map'; return gulp.src(['src/prebid.js']) .pipe(webpack(webpackConfig)) + .pipe(replace('$prebid.version$', prebid.version)) .pipe(gulp.dest('build/dev')) .pipe(connect.reload()); }); @@ -58,6 +60,7 @@ gulp.task('webpack', function () { return gulp.src(['src/prebid.js']) .pipe(webpack(webpackConfig)) + .pipe(replace('$prebid.version$', prebid.version)) .pipe(uglify()) .pipe(header(banner, { prebid: prebid })) .pipe(gulp.dest('build/dist')) diff --git a/src/prebid.js b/src/prebid.js index 2bb1fb20977..06278768a75 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -42,8 +42,8 @@ pbjs.logging = pbjs.logging || false; //let the world know we are loaded pbjs.libLoaded = true; -//TODO: this should be auto generated from build -utils.logInfo('Prebid.js v0.7.0 loaded'); +//version auto generated from build +utils.logInfo('Prebid.js v$prebid.version$ loaded'); //create adUnit array pbjs.adUnits = pbjs.adUnits || []; From ad434a524bc1734b697719d0c5189e7da5b7503a Mon Sep 17 00:00:00 2001 From: Jeremy Blencowe Date: Thu, 21 Apr 2016 15:07:05 -0700 Subject: [PATCH 098/160] Add bidfloor parameter to AOL adapter (#324) * Add bidfloor parameter to AOL adapter --- src/adapters/aol.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/adapters/aol.js b/src/adapters/aol.js index fff201e39f6..c064ec114a2 100644 --- a/src/adapters/aol.js +++ b/src/adapters/aol.js @@ -140,7 +140,8 @@ var AolAdapter = function AolAdapter() { }, params: { cors: 'yes', - cmd: 'bid' + cmd: 'bid', + bidfloor: (typeof bid.params.bidFloor !== "undefined") ? bid.params.bidFloor.toString() : '' }, pubApiConfig: ADTECH_PUBAPI_CONFIG, placementCode: bid.placementCode From 1045c1ada9a041f24d3ecb71424d143a792e9bcd Mon Sep 17 00:00:00 2001 From: Nate Guisinger Date: Fri, 22 Apr 2016 08:10:15 -0700 Subject: [PATCH 099/160] enableSendAllBids API method (#328) --- src/constants.json | 8 ++++++- src/prebid.js | 41 +++++++++++++++++++++++++++++---- test/spec/unit/pbjs_api_spec.js | 26 ++++++++++++++++++++- 3 files changed, 68 insertions(+), 7 deletions(-) diff --git a/src/constants.json b/src/constants.json index 72aab6ffdb3..d6a7c008c5e 100644 --- a/src/constants.json +++ b/src/constants.json @@ -42,5 +42,11 @@ "MEDIUM": "medium", "HIGH": "high", "AUTO": "auto" - } + }, + "TARGETING_KEYS": [ + "hb_bidder", + "hb_adid", + "hb_pb", + "hb_size" + ] } diff --git a/src/prebid.js b/src/prebid.js index 06278768a75..cecedcdb54d 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -1,6 +1,6 @@ /** @module pbjs */ -// if pbjs already exists in global dodcument scope, use it, if not, create the object +// if pbjs already exists in global document scope, use it, if not, create the object window.pbjs = (window.pbjs || {}); window.pbjs.que = window.pbjs.que || []; var pbjs = window.pbjs; @@ -28,6 +28,7 @@ var pb_bidderMap = {}; var pb_targetingMap = {}; var pb_keyHistoryMap = {}; var pb_bidsTimedOut = false; +var pb_sendAllBids = false; var eventValidators = { bidWon: checkDefinedPlacement @@ -212,7 +213,8 @@ function getWinningBid(bidArray) { function setGPTAsyncTargeting(code, slot) { //get the targeting that is already configured - var keyStrings = getTargetingfromGPTIdentifier(slot); + const keyStrings = getTargetingfromGPTIdentifier(slot); + const bids = pbjs.getBidResponses(slot.getAdUnitPath()); //copy keyStrings into pb_keyHistoryMap by code if (!pb_keyHistoryMap[code]) { @@ -221,6 +223,10 @@ function setGPTAsyncTargeting(code, slot) { utils.extend(pb_keyHistoryMap[code], keyStrings); } + if (pb_sendAllBids && bids && bids.bids && bids.bids.length) { + utils.extend(pb_keyHistoryMap[code], getTargetingKeysAsBidder(bids.bids)); + } + utils._each(pb_keyHistoryMap[code], function (value, key) { //since DFP doesn't support deleting a single key, we will set all to empty string //This is "clear" for that key @@ -277,7 +283,9 @@ function buildBidResponse(bidArray) { if (bid.alwaysUseBid && bidClone.adserverTargeting) { // add the bid if alwaysUse and bid has returned // push key into targeting pb_targetingMap[bidClone.adUnitCode] = utils.extend(pb_targetingMap[bidClone.adUnitCode], bidClone.adserverTargeting); - } if (bid.cpm && bid.cpm > 0) { + } + + if (bid.cpm && bid.cpm > 0) { //else put into auction array if cpm > 0 bidArrayTargeting.push({ cpm: bid.cpm, @@ -519,9 +527,28 @@ function getTargetingfromGPTIdentifier(slot) { } /** + * returns targeting keys with key name appended with the bidder code + * @param bidArray an array of current bid objects + */ +function getTargetingKeysAsBidder(bidArray) { + const standardKeys = CONSTANTS.TARGETING_KEYS; + let pairs = {}; + + // this assumes no key name collisions, which should not be possible, + // because bidder names are constrained by the adapter filename + // so uniqueness is enforced by the file system. + utils._each(bidArray, bid => { + if (bid.adserverTargeting) { + utils._each(standardKeys, key => { + pairs[`${key}_${bid.bidderCode}`] = bid.adserverTargeting[key]; + }); + } + }); + return pairs; +} - /** +/** * Set query string targeting on all GPT ad units. * @alias module:pbjs.setTargetingForGPTAsync */ @@ -599,7 +626,7 @@ pbjs.renderAd = function (doc, id) { }; /** - * @deprecated - will be removed next release. Use pbjs.requestBids + * @deprecated - will be removed next release. Use pbjs.requestBids */ pbjs.requestBidsForAdUnit = function (adUnitCode) { resetBids(); @@ -967,4 +994,8 @@ pbjs.setPriceGranularity = function (granularity) { } }; +pbjs.enableSendAllBids = function () { + pb_sendAllBids = true; +}; + processQue(); diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index 08c7a947f23..0f3e0edbca0 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -57,7 +57,7 @@ after(function () { utils.logMessage.restore(); }); -describe('Unit: Prebid API', function () { +describe('Unit: Prebid Module', function () { describe('getAdserverTargetingForAdUnitCodeStr', function () { it('should return targeting info as a string', function () { var result = pbjs.getAdserverTargetingForAdUnitCodeStr(config.adUnitCodes[0]); @@ -158,6 +158,30 @@ describe('Unit: Prebid API', function () { assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_pb', '10.00'), 'sets hb_pb param'); assert.ok(slots[0].spySetTargeting.calledWithExactly('foobar', '300x250'), 'sets foobar param'); }); + + it('Calling enableSendAllBids should set targeting to include standard keys with bidder' + + ' append to key name', function() { + var slots = createSlotArray(); + window.googletag.pubads().setSlots(slots); + + pbjs.enableSendAllBids(); + pbjs.setTargetingForGPTAsync(); + assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_bidder', ''), 'clears hb_bidder param'); + assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_adid', ''), 'clears hb_adid param'); + assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_pb', ''), 'clears hb_pb param'); + assert.ok(slots[0].spySetTargeting.calledWithExactly('foobar', ''), 'clears foobar param'); + assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_bidder', 'rubicon'), 'sets hb_bidder param'); + assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_adid', '148018fe5e'), 'sets hb_adid param'); + assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_pb', '10.00'), 'sets hb_pb param'); + assert.ok(slots[0].spySetTargeting.calledWithExactly('foobar', '300x250'), 'sets foobar param'); + assert.ok(slots[0].spySetTargeting.calledWith('hb_bidder_rubicon', ''), 'sets' + + ' hb_bidder_rubicon param'); + assert.ok(slots[0].spySetTargeting.calledWith('hb_adid_rubicon', ''), 'sets hb_adid_rubicon param'); + assert.ok(slots[0].spySetTargeting.calledWith('hb_pb_rubicon', ''), 'sets hb_pb_rubicon' + + ' param'); + assert.ok(!slots[0].spySetTargeting.calledWithExactly('foobar_rubicon', ''), 'does' + + ' not set custom keys with bidder in key name for foobar_rubicon param'); + }); }); describe('allBidsAvailable', function () { From a85404f757338c650bce25f6b0da08707b6e8766 Mon Sep 17 00:00:00 2001 From: protonate Date: Fri, 22 Apr 2016 15:06:09 -0700 Subject: [PATCH 100/160] Prebid 0.8.1 Release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c30e13c5624..5965b0aa6d5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "0.8.0", + "version": "0.8.1", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 1c409a30dec8b471af56513a484b81816f5f4d4f Mon Sep 17 00:00:00 2001 From: mkendall07 Date: Thu, 28 Apr 2016 13:41:36 -0400 Subject: [PATCH 101/160] fix issue #337 - enable requesting to DFP even if prebid.js isn't loaded yet. --- integrationExamples/gpt/pbjs_example_gpt.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integrationExamples/gpt/pbjs_example_gpt.html b/integrationExamples/gpt/pbjs_example_gpt.html index 3168ecfb5c3..5384bf6cc0a 100644 --- a/integrationExamples/gpt/pbjs_example_gpt.html +++ b/integrationExamples/gpt/pbjs_example_gpt.html @@ -21,8 +21,8 @@ googletag.cmd.push(function () { pbjs.que.push(function () { pbjs.setTargetingForGPTAsync(); - googletag.pubads().refresh(); }); + googletag.pubads().refresh(); }); pbjs.initAdserverSet = true; } From 702903230a1d95062a63295543e9cf722abdb43f Mon Sep 17 00:00:00 2001 From: Matt Kendall Date: Mon, 2 May 2016 17:09:15 -0400 Subject: [PATCH 102/160] Add isitmaintained.com badges (#341) --- .travis.yml | 5 ++--- README.md | 5 ++++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1ba876fd2c3..3a4bbe8f978 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,10 @@ language: node_js node_js: - - 0.12 - - 0.11 + - "5.1" before_install: - npm install -g gulp script: - - gulp build \ No newline at end of file + - gulp build diff --git a/README.md b/README.md index ee2f789c6c4..b258112b3e9 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ +[![Percentage of issues still open](http://isitmaintained.com/badge/open/prebid/Prebid.js.svg)](http://isitmaintained.com/project/prebid/Prebid.js "Percentage of issues still open") +[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/prebid/Prebid.js.svg)](http://isitmaintained.com/project/prebid/Prebid.js "Average time to resolve an issue") + Prebid.js ======== @@ -88,7 +91,7 @@ Full Developer API reference: Contribute ---------- -**Note:** You need to have at least `node.js 4.x` or greater installed to be able to run the gulp build commands. +**Note:** You need to have at least `node.js 4.x` or greater installed to be able to run the gulp build commands. ### Add a Bidder Adapter ### Follow the [guide outlined here](http://prebid.org/dev-docs/bidder-adaptor.html) to add an adapter. From e01c7b0c9b0838527cd19d1c1f232098275b3c55 Mon Sep 17 00:00:00 2001 From: mkendall07 Date: Mon, 2 May 2016 17:23:23 -0400 Subject: [PATCH 103/160] Add travis-ci badge --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b258112b3e9..ead5b589f1b 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +[![Build Status](https://travis-ci.org/prebid/Prebid.js.svg?branch=master)](https://travis-ci.org/prebid/Prebid.js) [![Percentage of issues still open](http://isitmaintained.com/badge/open/prebid/Prebid.js.svg)](http://isitmaintained.com/project/prebid/Prebid.js "Percentage of issues still open") [![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/prebid/Prebid.js.svg)](http://isitmaintained.com/project/prebid/Prebid.js "Average time to resolve an issue") From aed499d5b6235070f062a97e536891b6139db8e2 Mon Sep 17 00:00:00 2001 From: mkendall07 Date: Tue, 3 May 2016 08:49:06 -0400 Subject: [PATCH 104/160] Address #171 - updated code example. --- README.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index ead5b589f1b..b094b3021ed 100644 --- a/README.md +++ b/README.md @@ -40,12 +40,14 @@ Download the integration example [here](https://github.com/prebid/Prebid.js/blob **Include the prebid.js library** Note that you need to host `prebid.js` locally or on a CDN and update the reference in the code snippet below for `cdn.host.com/prebid.min.js ```javascript -(function() { - var d = document, pbs = d.createElement('script'), pro = d.location.protocol; - pbs.type = 'text/javascript'; - pbs.src = ((pro === 'https:') ? 'https' : 'http') + '://cdn.host.com/prebid.min.js'; - var target = document.getElementsByTagName('head')[0]; - target.insertBefore(pbs, target.firstChild); +(function () { + var d = document; + var pbs = d.createElement('script'); + pbs.type = 'text/javascript'; + //replace with your CDN hosted version. HTTPS is strongly recommended. + pbs.src = '//cdn.host.com/prebid.min.js'; + var target = d.getElementsByTagName('script')[0]; + target.parentNode.insertBefore(pbs, target); })(); ``` From 7059d44ac0f364ff4c778d8d92f7f6263255a1e1 Mon Sep 17 00:00:00 2001 From: mkendall07 Date: Tue, 3 May 2016 10:50:15 -0400 Subject: [PATCH 105/160] Update line item creative code to handle cases when window.top does not hold pbjs --- integrationExamples/gpt/creative_rendering.html | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/integrationExamples/gpt/creative_rendering.html b/integrationExamples/gpt/creative_rendering.html index 6379b78bfc1..7b972b86400 100644 --- a/integrationExamples/gpt/creative_rendering.html +++ b/integrationExamples/gpt/creative_rendering.html @@ -1,3 +1,20 @@ + + + + From f098fbe72a97d344aa2483c9294fff8fbea32932 Mon Sep 17 00:00:00 2001 From: mkendall07 Date: Tue, 3 May 2016 11:09:52 -0400 Subject: [PATCH 106/160] Add code climate badge --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b094b3021ed..de09dc6902f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ [![Build Status](https://travis-ci.org/prebid/Prebid.js.svg?branch=master)](https://travis-ci.org/prebid/Prebid.js) [![Percentage of issues still open](http://isitmaintained.com/badge/open/prebid/Prebid.js.svg)](http://isitmaintained.com/project/prebid/Prebid.js "Percentage of issues still open") [![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/prebid/Prebid.js.svg)](http://isitmaintained.com/project/prebid/Prebid.js "Average time to resolve an issue") +[![Code Climate](https://codeclimate.com/github/prebid/Prebid.js/badges/gpa.svg)](https://codeclimate.com/github/prebid/Prebid.js) Prebid.js ======== From 2b5d5fc7395ad08eff84e7d0c1327b9335b82c1a Mon Sep 17 00:00:00 2001 From: Jeremy Marzka Date: Tue, 3 May 2016 12:21:49 -0400 Subject: [PATCH 107/160] Only call OpenX setPageURL, setRefererURL, and addPage if their options are set --- src/adapters/openx.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/adapters/openx.js b/src/adapters/openx.js index 8929bfe8642..cdc7d571822 100644 --- a/src/adapters/openx.js +++ b/src/adapters/openx.js @@ -52,9 +52,17 @@ var OpenxAdapter = function OpenxAdapter(options) { var i; var POX = OX(); - POX.setPageURL(opts.pageURL); - POX.setRefererURL(opts.refererURL); - POX.addPage(opts.pgid); + if (opts.pageURL) { + POX.setPageURL(opts.pageURL); + } + + if (opts.refererURL) { + POX.setRefererURL(opts.refererURL); + } + + if (opts.pgid) { + POX.addPage(opts.pgid); + } // Add each ad unit ID for (i = 0; i < bids.length; i++) { From 2d25b0a95da472a0bfad1672eee3dbb13e66a83e Mon Sep 17 00:00:00 2001 From: astudnicky Date: Fri, 29 Apr 2016 16:12:23 -0400 Subject: [PATCH 108/160] Update Sonobi adapter to accommodate single-size arrays Output errors for adops integrators Do not cancel bidding without sizes --- src/adapters/sonobi.js | 55 +++++++++++++++++++++++++++--------------- 1 file changed, 35 insertions(+), 20 deletions(-) diff --git a/src/adapters/sonobi.js b/src/adapters/sonobi.js index 6dc3d9fb37f..612f872711f 100644 --- a/src/adapters/sonobi.js +++ b/src/adapters/sonobi.js @@ -4,7 +4,7 @@ var adloader = require('../adloader.js'); var utils = require('../utils'); var SonobiAdapter = function SonobiAdapter(){ - var test = false; // tag tester = true || false + var test = false; // tag tester = true || false var cb_map = {}; function _phone_in(params){ @@ -13,36 +13,51 @@ var SonobiAdapter = function SonobiAdapter(){ adloader.loadScript(trinity + JSON.stringify(_keymaker(bids)) + '&cv=' + _operator(), null); } - function _keymaker(bids){ // Make keys + function _keymaker(bids){ // Make keys var keyring = {}; utils._each(bids, function(o){ - var sizes = []; - utils._each(o.sizes, function(size){ - sizes.push(size.join('x')); - }); - sizes = sizes.toString(); - if (!!o.params.ad_unit && o.params.ad_unit.length > 0) { - // Cypher - keyring[o.params.ad_unit + '|' + o.params.dom_id] = sizes; - cb_map[o.params.ad_unit + '|' + o.params.dom_id] = o.placementCode; - } else if (!!o.params.placement_id && o.params.placement_id.length > 0) { - // Morpheus - keyring[o.params.dom_id] = o.params.placement_id + (test ? '-test' : '') + '|' + sizes; - cb_map[o.params.dom_id] = o.placementCode; - } else { - utils.logError('sonobi unable to bid: Improper parameters for ' + o.placementCode); + var sizes = utils.parseSizesInput(o.sizes).toString(); + if (utils.isEmpty(sizes)){ + utils.logWarn('Sonobi adapter expects sizes for ' + o.placementCode); + } + switch(true){ + case (!o.params.ad_unit && !o.params.placement_id): + utils.logError('Sonobi unable to bid: Missing parameters for ' + o.placementCode); + break; + case (!!o.params.ad_unit && !!o.params.placement_id): + utils.logError('Sonobi unable to bid: Extra parameters for ' + o.placementCode); + break; + case (!!o.params.ad_unit && o.params.ad_unit.length === 0): + utils.logError('Sonobi unable to bid: Empty ad_unit for ' + o.placementCode); + break; + case (!!o.params.placement_id && o.params.placement_id.length === 0): + utils.logError('Sonobi unable to bid: Empty placement_id for ' + o.placementCode); + break; + case (!!o.params.placement_id): // Morpeus style + keyring[o.params.dom_id] = o.params.placement_id + (test ? '-test' : '') + '|' + sizes; + cb_map[o.params.dom_id] = o.placementCode; + break; + case (!!o.params.ad_unit && o.params.ad_unit.charAt(0) !== '/'): + // DFP docs do not necessarily require leading slash? - add it in if it's not there. + o.params.ad_unit = '/' + o.params.ad_unit; + case (!!o.params.ad_unit): // Cypher style + keyring[o.params.ad_unit + '|' + o.params.dom_id] = sizes; + cb_map[o.params.ad_unit + '|' + o.params.dom_id] = o.placementCode; + break; + default: // I don't know how it's broken, but it is. + utils.logError('Sonobi unable to bid: Improper parameters for ' + o.placementCode); } }); return keyring; } - function _operator(){ // Uniqify callbacks + function _operator(){ // Uniqify callbacks var uniq = "cb" + utils.getUniqueIdentifierStr(); window[uniq] = _trinity; return uniq; } - function _trinity(response){ // Callback + function _trinity(response){ // Callback var slots = response.slots || {}; var sbi_dc = response.sbi_dc || ''; var bidObject = {}; @@ -55,7 +70,7 @@ var SonobiAdapter = function SonobiAdapter(){ bidObject.width = Number(slots[slot].sbi_size.split('x')[0]); bidObject.height = Number(slots[slot].sbi_size.split('x')[1]); bidmanager.addBidResponse(cb_map[slot], bidObject); - } else { // No aid? No ad. + } else { // No aid? No ad. bidObject = bidfactory.createBid(2); bidObject.bidderCode = 'sonobi'; bidmanager.addBidResponse(cb_map[slot], bidObject); From a42cbd0c8ea04baa228d4c2ba8df145ec7d0bcda Mon Sep 17 00:00:00 2001 From: Mike Miller Date: Mon, 9 May 2016 14:56:21 -0400 Subject: [PATCH 109/160] Don't set targeting for CPM of zero (#331) * Don't set targeting for CPM of zero --- src/prebid.js | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/prebid.js b/src/prebid.js index 12bf6dc681b..b9b35a0baa1 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -195,20 +195,19 @@ function storeBidRequestByBidder(placementCode, sizes, bids) { } function getWinningBid(bidArray) { - var winningBid = {}; if (bidArray && bidArray.length !== 0) { bidArray.sort(function (a, b) { //put the highest CPM first return b.cpm - a.cpm; }); - //the first item has the highest cpm - winningBid = bidArray[0]; - - //TODO : if winning bid CPM === 0 - we need to indicate no targeting should be set + // The first item has the highest cpm + // If winning bid CPM === 0 - we need to indicate no targeting should be set + if (bidArray[0].cpm === 0 ) { + return null; + } + return bidArray[0].bid; } - - return winningBid.bid; } function setGPTAsyncTargeting(code, slot) { @@ -301,8 +300,12 @@ function buildBidResponse(bidArray) { // push the winning bid into targeting map if (adUnitCode && bidArrayTargeting.length !== 0) { var winningBid = getWinningBid(bidArrayTargeting); - var keyValues = winningBid.adserverTargeting; - pb_targetingMap[adUnitCode] = utils.extend(pb_targetingMap[adUnitCode], keyValues); + if (winningBid) { + var keyValues = winningBid.adserverTargeting; + pb_targetingMap[adUnitCode] = utils.extend(pb_targetingMap[adUnitCode], keyValues); + } else { + utils.logWarn('No winning bids available.'); + } } return bidResponseArray; From c801d86deb483b89983080978adbc6ebd80f71ac Mon Sep 17 00:00:00 2001 From: Matt Lane Date: Wed, 11 May 2016 14:28:01 -0700 Subject: [PATCH 110/160] Test pbjs.renderAd public api function (#344) --- test/spec/unit/pbjs_api_spec.js | 83 +++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index 0f3e0edbca0..2365c7e13f9 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -193,4 +193,87 @@ describe('Unit: Prebid Module', function () { bidmanager.allBidsBack.restore(); }); }); + + describe('renderAd', function () { + var bidId = 1; + var doc = {}; + var adResponse = {}; + var spyLogError = null; + + beforeEach(function() { + doc = { + write: sinon.spy(), + close: sinon.spy(), + defaultView: { + frameElement: { + width: 0, + height: 0 + } + } + }; + + adResponse = { + "width": 300, + "height": 250, + }; + bidmanager._adResponsesByBidderId[bidId] = adResponse; + + spyLogError = sinon.spy(utils, 'logError'); + }); + + afterEach(function() { + bidmanager._adResponsesByBidderId[bidId] = null; + utils.logError.restore(); + }); + + it('should require doc and id params', function () { + pbjs.renderAd(); + var error = 'Error trying to write ad Id :undefined to the page. Missing document or adId'; + assert.ok(spyLogError.calledWith(error), 'expected param error was logged'); + }); + + it('should log message with bid id', function () { + pbjs.renderAd(doc, bidId); + var message = 'Calling renderAd with adId :' + bidId; + assert.ok(spyLogMessage.calledWith(message), 'expected message was logged'); + }); + + it('should write the ad to the doc', function() { + adResponse.ad = ""; + pbjs.renderAd(doc, bidId); + assert.ok(doc.write.calledWith(adResponse.ad), 'ad was written to doc'); + assert.ok(doc.close.called, 'close method called'); + }); + + it('should place the url inside an iframe on the doc', function() { + adResponse.adUrl = "http://server.example.com/ad/ad.js"; + pbjs.renderAd(doc, bidId); + var iframe = '' + assert.ok(doc.write.calledWith(iframe), 'url was written to iframe in doc'); + }); + + it('should log an error when no ad or url', function() { + pbjs.renderAd(doc, bidId); + var error = 'Error trying to write ad. No ad for bid response id: ' + bidId; + assert.ok(spyLogError.calledWith(error), 'expected error was logged'); + }); + + it('should catch errors thrown when trying to write ads to the page', function() { + adResponse.ad = ""; + + var error = {message: 'doc write error'}; + doc.write = sinon.stub().throws(error); + pbjs.renderAd(doc, bidId); + + var errorMessage = 'Error trying to write ad Id :' + bidId + ' to the page:' + error.message; + assert.ok(spyLogError.calledWith(errorMessage), 'expected error was logged'); + }); + + it('should log an error when ad not found', function() { + var fakeId = 99; + pbjs.renderAd(doc, fakeId); + var error = 'Error trying to write ad. Cannot find ad by given id : ' + fakeId; + assert.ok(spyLogError.calledWith(error), 'expected error was logged'); + }); + }); }); From bb0575404c684dbbd4d7064e89cb659b51b2d651 Mon Sep 17 00:00:00 2001 From: mkendall07 Date: Thu, 12 May 2016 14:01:50 -0400 Subject: [PATCH 111/160] Fix `pbjs.enableSendAllBids()` when using DivId for your adunit.code. --- src/prebid.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/prebid.js b/src/prebid.js index b9b35a0baa1..38ac786eeed 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -213,7 +213,11 @@ function getWinningBid(bidArray) { function setGPTAsyncTargeting(code, slot) { //get the targeting that is already configured const keyStrings = getTargetingfromGPTIdentifier(slot); - const bids = pbjs.getBidResponses(slot.getAdUnitPath()); + //this is a temp fix until we merge the refactor + var bids = pbjs.getBidResponses(slot.getAdUnitPath()); + if(bids.bids.length === 0) { + bids = pbjs.getBidResponses(slot.getSlotElementId()); + } //copy keyStrings into pb_keyHistoryMap by code if (!pb_keyHistoryMap[code]) { From 197a81d9b59ec8edb14dcc78039d273b279cfe83 Mon Sep 17 00:00:00 2001 From: Kyle Slattery Date: Tue, 17 May 2016 16:40:11 -0400 Subject: [PATCH 112/160] Fix "Add a Bidder Adapter" link in README (#346) The previous link was going to a nonexistent anchor. I fixed the text and link so it now works. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index de09dc6902f..eff6d81a28e 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ No more week-long development. Header bidding is made easy by prebid.js :) - [Example code](#example-code) - [API](#api) - [Contribute](#contribute) - - [Add an Bidder Adapter](#add-an-bidder-adapter) + - [Add a Bidder Adapter](#add-a-bidder-adapter) - [install](#install) - [Build](#build) - [Configure](#configure) From 2ad8150b73110c199bfc73f916589b3f3f016a7f Mon Sep 17 00:00:00 2001 From: Nate Guisinger Date: Tue, 17 May 2016 18:06:11 -0700 Subject: [PATCH 113/160] Refactor of Prebid (#349) * Concurrent bid requests * remove small helper functions for readability * remove bidmanager.setBidderMap (not used) * remove createEmptyBidResponseObj (just return object literal) * remove pb_preBidders as unused * refactor pbjs.requestBids and adaptermanager.callBids * refactor bid count, rearrange getPriceBucketStrings * refactor of targeting to set and clear before and after ad server call * ad being rendered, still in progress * fix bug in appnexus adapter, lower timeout duration * notes * render ads as soon as all bids are back * remove deprecated code * add small composable functions for readability, comment out a few API methods * comment API methods and related tests * adds fixtures, refactor tests, comment some api tests until methods are restored * restore API methods, add fixtures functions, fix tests * restore all adpaters, remove deprecated code and tests * review notes * review notes: naming things, restore api methods, move composables to utils * exclude adapters from coverage reporting * include adapters in coverage reporting * restore `bidsAvailableForAdapter` API method, move `getBidRequest` to utils * restore test for `getAdserverTargetingForAdUnitCodeStr` * Fix `pbjs.enableSendAllBids()` when using DivId for your adunit.code. * fix object destructuring bug in `pbjs.requestBids` method signature * bug fixes: allBidsAvailable should return boolean, wrap one time external callback in array, object destructuring in requestBids method signature restored * move `getKeys` and `getVal` to utils module, rename `bidSet` to `bidderRequest` * Derive bidder codes from adUnits, fixes "Calling All Bidders" bug, also move `getBidderCodes` to utils * fix `getAdServerTargeting` test * Fix token illegal error * Concurrent bid requests * remove small helper functions for readability * remove bidmanager.setBidderMap (not used) * remove createEmptyBidResponseObj (just return object literal) * remove pb_preBidders as unused * refactor pbjs.requestBids and adaptermanager.callBids * refactor bid count, rearrange getPriceBucketStrings * refactor of targeting to set and clear before and after ad server call * ad being rendered, still in progress * fix bug in appnexus adapter, lower timeout duration * notes * render ads as soon as all bids are back * remove deprecated code * add small composable functions for readability, comment out a few API methods * comment API methods and related tests * adds fixtures, refactor tests, comment some api tests until methods are restored * restore API methods, add fixtures functions, fix tests * restore all adpaters, remove deprecated code and tests * review notes * review notes: naming things, restore api methods, move composables to utils * exclude adapters from coverage reporting * include adapters in coverage reporting * restore `bidsAvailableForAdapter` API method, move `getBidRequest` to utils * restore test for `getAdserverTargetingForAdUnitCodeStr` * fix object destructuring bug in `pbjs.requestBids` method signature * bug fixes: allBidsAvailable should return boolean, wrap one time external callback in array, object destructuring in requestBids method signature restored * move `getKeys` and `getVal` to utils module, rename `bidSet` to `bidderRequest` * Derive bidder codes from adUnits, fixes "Calling All Bidders" bug, also move `getBidderCodes` to utils * fix `getAdServerTargeting` test * Fix token illegal error * restore return value format for `pbjs.getBidResponses` * restore return format for `getAdServerTargeting()` * restore `getAdServerTargeting` return value as map merge `getBidResponses` to map some more helper functions fixtures and tests * Restore getBidResponsesForAdUnitCode() * restore `getAdServerTargetingForAdUnitCodeStr` return value as string of querystring params more fixtures and tests * check if send all bids before adding bid landscape targeting * Implement 'dense' price bucket mode - Don't export getPriceBucketString - Test 'dense' price bucket mode integration --- .jscsrc | 1 - src/adaptermanager.js | 63 +- src/adapters/adform.js | 3 +- src/adapters/appnexus.js | 17 +- src/adapters/brightcom.js | 6 +- src/adapters/indexExchange.js | 2 +- src/adapters/nginad.js | 5 +- src/adapters/pubmatic.js | 2 +- src/adapters/sovrn.js | 4 +- src/adapters/springserve.js | 12 +- src/adapters/triplelift.js | 15 +- src/bidfactory.js | 4 +- src/bidmanager.js | 382 ++++------- src/constants.json | 3 +- src/polyfills.js | 74 -- src/prebid.js | 708 ++++--------------- src/utils.js | 117 ++-- test/fixtures/config.json | 2 +- test/fixtures/fixtures.js | 1105 ++++++++++++++++++++++++++++++ test/fixtures/targeting-map.json | 33 +- test/spec/bidmanager_spec.js | 44 ++ test/spec/unit/pbjs_api_spec.js | 167 ++--- test/spec/utils_spec.js | 13 +- 23 files changed, 1661 insertions(+), 1121 deletions(-) delete mode 100644 src/polyfills.js create mode 100644 test/fixtures/fixtures.js diff --git a/.jscsrc b/.jscsrc index d4cbfa18c97..2b70851dd97 100644 --- a/.jscsrc +++ b/.jscsrc @@ -1,6 +1,5 @@ { "maxErrors": 1000, - "esnext": true, "requireTrailingComma": null, "requireCamelCaseOrUpperCaseIdentifiers": null, "requireSpacesInAnonymousFunctionExpression": null, diff --git a/src/adaptermanager.js b/src/adaptermanager.js index 5bd70733ea5..4f4c7a32b54 100644 --- a/src/adaptermanager.js +++ b/src/adaptermanager.js @@ -1,6 +1,7 @@ /** @module adaptermanger */ -var bidmanager = require('./bidmanager.js'); +import { flatten, getBidderCodes } from './utils'; + var utils = require('./utils.js'); var CONSTANTS = require('./constants.json'); var events = require('./events'); @@ -9,34 +10,44 @@ import { BaseAdapter } from './adapters/baseAdapter'; var _bidderRegistry = {}; exports.bidderRegistry = _bidderRegistry; -exports.callBids = function (bidderArr) { - for (var i = 0; i < bidderArr.length; i++) { - //use the bidder code to identify which function to call - var bidder = bidderArr[i]; - if (bidder.bidderCode && _bidderRegistry[bidder.bidderCode]) { - utils.logMessage('CALLING BIDDER ======= ' + bidder.bidderCode); - var currentBidder = _bidderRegistry[bidder.bidderCode]; - - //emit 'bidRequested' event - events.emit(CONSTANTS.EVENTS.BID_REQUESTED, bidder); - currentBidder.callBids(bidder); - - // if the bidder didn't explicitly set the number of bids - // expected, default to the number of bids passed into the bidder - if (bidmanager.getExpectedBidsCount(bidder.bidderCode) === undefined) { - bidmanager.setExpectedBidsCount(bidder.bidderCode, bidder.bids.length); - } - - var currentTime = new Date().getTime(); - bidmanager.registerBidRequestTime(bidder.bidderCode, currentTime); - - if (currentBidder.defaultBidderSettings) { - bidmanager.registerDefaultBidderSetting(bidder.bidderCode, currentBidder.defaultBidderSettings); +function getBids({ bidderCode, requestId, bidderRequestId }) { + return pbjs.adUnits.map(adUnit => { + return adUnit.bids.filter(bid => bid.bidder === bidderCode) + .map(bid => Object.assign(bid, { + placementCode: adUnit.code, + sizes: adUnit.sizes, + bidId: utils.getUniqueIdentifierStr(), + bidderRequestId, + requestId + })); + }).reduce(flatten, []); +} + +exports.callBids = () => { + const requestId = utils.getUniqueIdentifierStr(); + + getBidderCodes().forEach(bidderCode => { + const adapter = _bidderRegistry[bidderCode]; + if (adapter) { + const bidderRequestId = utils.getUniqueIdentifierStr(); + const bidderRequest = { + bidderCode, + requestId, + bidderRequestId, + bids: getBids({ bidderCode, requestId, bidderRequestId }), + start: new Date().getTime() + }; + console.log('bid set:', bidderCode, bidderRequestId); + utils.logMessage(`CALLING BIDDER ======= ${bidderCode}`); + pbjs._bidsRequested.push(bidderRequest); + events.emit(CONSTANTS.EVENTS.BID_REQUESTED, bidderRequest); + if (bidderRequest.bids && bidderRequest.bids.length) { + adapter.callBids(bidderRequest); } } else { - utils.logError('Adapter trying to be called which does not exist: ' + bidder.bidderCode, 'adaptermanager.callBids'); + utils.logError(`Adapter trying to be called which does not exist: ${bidderCode} adaptermanager.callBids`); } - } + }); }; exports.registerBidAdapter = function (bidAdaptor, bidderCode) { diff --git a/src/adapters/adform.js b/src/adapters/adform.js index dc9bbfa9bc8..001d084ab32 100644 --- a/src/adapters/adform.js +++ b/src/adapters/adform.js @@ -10,11 +10,12 @@ function AdformAdapter() { }; function _callBids(params) { - var callbackName = '_adf_' + utils.getUniqueIdentifierStr(); + //var callbackName = '_adf_' + utils.getUniqueIdentifierStr(); var bid; var noDomain = true; var bids = params.bids; var request = []; + var callbackName = '_adf_' + utils.getUniqueIdentifierStr(); for (var i = 0, l = bids.length; i < l; i++) { bid = bids[i]; diff --git a/src/adapters/appnexus.js b/src/adapters/appnexus.js index 5975001ae27..23b37f86840 100644 --- a/src/adapters/appnexus.js +++ b/src/adapters/appnexus.js @@ -1,3 +1,5 @@ +import { getBidRequest } from '../utils.js'; + var CONSTANTS = require('../constants.json'); var utils = require('../utils.js'); var adloader = require('../adloader.js'); @@ -10,21 +12,22 @@ AppNexusAdapter = function AppNexusAdapter() { var baseAdapter = Adapter.createNew('appnexus'); baseAdapter.callBids = function (params) { - var bidCode = baseAdapter.getBidderCode(); + //var bidCode = baseAdapter.getBidderCode(); var anArr = params.bids; - var bidsCount = anArr.length; + + //var bidsCount = anArr.length; //set expected bids count for callback execution - bidmanager.setExpectedBidsCount(bidCode, bidsCount); + //bidmanager.setExpectedBidsCount(bidCode, bidsCount); - for (var i = 0; i < bidsCount; i++) { + for (var i = 0; i < anArr.length; i++) { var bidRequest = anArr[i]; - var callbackId = utils.getUniqueIdentifierStr(); + var callbackId = bidRequest.bidId; adloader.loadScript(buildJPTCall(bidRequest, callbackId)); //store a reference to the bidRequest from the callback id - bidmanager.pbCallbackMap[callbackId] = bidRequest; + //bidmanager.pbCallbackMap[callbackId] = bidRequest; } }; @@ -149,7 +152,7 @@ AppNexusAdapter = function AppNexusAdapter() { var responseCPM; var id = jptResponseObj.callback_uid; var placementCode = ''; - var bidObj = bidmanager.getPlacementIdByCBIdentifer(id); + var bidObj = getBidRequest(id); if (bidObj) { bidCode = bidObj.bidder; diff --git a/src/adapters/brightcom.js b/src/adapters/brightcom.js index 76d3bbdd8b4..72ba7a4a4ca 100644 --- a/src/adapters/brightcom.js +++ b/src/adapters/brightcom.js @@ -83,7 +83,7 @@ var BrightcomAdapter = function BrightcomAdapter() { // Add current impression to collection brightcomImps.push(imp); // Add mapping to current bid via impression id - bidmanager.pbCallbackMap[imp.id] = bid; + //bidmanager.pbCallbackMap[imp.id] = bid; // Add current ad unit's code to tracking reqAdUnitsCode.push(bid.placementCode); @@ -136,8 +136,8 @@ var BrightcomAdapter = function BrightcomAdapter() { brightcomResponseObj.seatbid[0].bid.forEach( function(curBid) { // Get the bid request data - var bidRequest = bidmanager.getPlacementIdByCBIdentifer(curBid.impid); - + var bidRequest = pbjs._bidsRequested.find(bidSet => bidSet.bidderCode === 'brightcom').bids[0]; // this assumes a single request only + // Make sure the bid exists if (bidRequest) { diff --git a/src/adapters/indexExchange.js b/src/adapters/indexExchange.js index 67f5851ff07..7f90c0511b5 100644 --- a/src/adapters/indexExchange.js +++ b/src/adapters/indexExchange.js @@ -360,7 +360,7 @@ var IndexExchangeAdapter = function IndexExchangeAdapter() { utils.logError('Too many unique sizes on slots, will use the first 20.', ADAPTER_NAME); } - bidmanager.setExpectedBidsCount(ADAPTER_CODE, expectedBids); + //bidmanager.setExpectedBidsCount(ADAPTER_CODE, expectedBids); adloader.loadScript(cygnus_index_start()); var responded = false; diff --git a/src/adapters/nginad.js b/src/adapters/nginad.js index 931f5f8457a..596dd9de134 100644 --- a/src/adapters/nginad.js +++ b/src/adapters/nginad.js @@ -84,7 +84,7 @@ var NginAdAdapter = function NginAdAdapter() { }; nginadImps.push(imp); - bidmanager.pbCallbackMap[imp.id] = bid; + //bidmanager.pbCallbackMap[imp.id] = bid; rtbServerDomain = bid.params.nginadDomain; @@ -141,7 +141,8 @@ var NginAdAdapter = function NginAdAdapter() { var id = nginadBid.impid; // try to fetch the bid request we sent NginAd - var bidObj = bidmanager.getPlacementIdByCBIdentifer(id); + var bidObj = pbjs._bidsRequested.find(bidSet => bidSet.bidderCode === 'nginad').bids + .filter(bid => bid.params && bid.params.impId === id); if (!bidObj) { return handleErrorResponse(nginadBid, defaultPlacementForBadBid); } diff --git a/src/adapters/pubmatic.js b/src/adapters/pubmatic.js index 20a9e244434..4ae81941f72 100644 --- a/src/adapters/pubmatic.js +++ b/src/adapters/pubmatic.js @@ -18,7 +18,7 @@ var PubmaticAdapter = function PubmaticAdapter() { bids = params.bids; for (var i = 0; i < bids.length; i++) { var bid = bids[i]; - bidmanager.pbCallbackMap['' + bid.params.adSlot] = bid; + //bidmanager.pbCallbackMap['' + bid.params.adSlot] = bid; _pm_pub_id = _pm_pub_id || bid.params.publisherId; _pm_optimize_adslots.push(bid.params.adSlot); } diff --git a/src/adapters/sovrn.js b/src/adapters/sovrn.js index 2e56e1fa439..be3b90bcf4a 100644 --- a/src/adapters/sovrn.js +++ b/src/adapters/sovrn.js @@ -55,7 +55,7 @@ var SovrnAdapter = function SovrnAdapter() { bidfloor: bidFloor }; sovrnImps.push(imp); - bidmanager.pbCallbackMap[imp.id] = bid; + //bidmanager.pbCallbackMap[imp.id] = bid; allPlacementCodes.push(bid.placementCode); }); @@ -105,7 +105,7 @@ var SovrnAdapter = function SovrnAdapter() { var bid = {}; // try to fetch the bid request we sent Sovrn - var bidObj = bidmanager.getPlacementIdByCBIdentifer(id); + var bidObj = pbjs._bidsRequested.map(bidSet => bidSet.bids.filter(bid => bid.params && bid.params.impId === id)); if (bidObj) { placementCode = bidObj.placementCode; placementsWithBidsBack.push(placementCode); diff --git a/src/adapters/springserve.js b/src/adapters/springserve.js index 31f03fdb0c7..9babe694e36 100644 --- a/src/adapters/springserve.js +++ b/src/adapters/springserve.js @@ -57,7 +57,7 @@ SpringServeAdapter = function SpringServeAdapter() { var bids = params.bids || []; for (var i = 0; i < bids.length; i++) { var bid = bids[i]; - bidmanager.pbCallbackMap[bid.params.impId] = params; + //bidmanager.pbCallbackMap[bid.params.impId] = params; adloader.loadScript(buildSpringServeCall(bid)); } } @@ -67,13 +67,15 @@ SpringServeAdapter = function SpringServeAdapter() { responseObj.seatbid[0].bid[0] !== undefined) { //look up the request attributs stored in the bidmanager var responseBid = responseObj.seatbid[0].bid[0]; - var requestObj = bidmanager.getPlacementIdByCBIdentifer(responseBid.impid); + //var requestObj = bidmanager.getPlacementIdByCBIdentifer(responseBid.impid); + var requestBids = pbjs._bidsRequested.find(bidSet => bidSet.bidderCode === 'springserve').bids + .filter(bid => bid.params && bid.params.impId === +responseBid.impid); var bid = bidfactory.createBid(1); var placementCode; //assign properties from the original request to the bid object - for (var i = 0; i < requestObj.bids.length; i++) { - var bidRequest = requestObj.bids[i]; + for (var i = 0; i < requestBids.length; i++) { + var bidRequest = requestBids[i]; if (bidRequest.bidder === 'springserve') { placementCode = bidRequest.placementCode; var size = bidRequest.sizes[0]; @@ -82,7 +84,7 @@ SpringServeAdapter = function SpringServeAdapter() { } } - bid.bidderCode = requestObj.bidderCode; + bid.bidderCode = requestBids[0].bidder; if (responseBid.hasOwnProperty('price') && responseBid.hasOwnProperty('adm')) { //assign properties from the response to the bid object diff --git a/src/adapters/triplelift.js b/src/adapters/triplelift.js index 4e8c9cd069a..e0083a95e16 100644 --- a/src/adapters/triplelift.js +++ b/src/adapters/triplelift.js @@ -15,14 +15,14 @@ var TripleLiftAdapter = function TripleLiftAdapter() { var bidsCount = tlReq.length; //set expected bids count for callback execution - bidmanager.setExpectedBidsCount('triplelift',bidsCount); + //bidmanager.setExpectedBidsCount('triplelift',bidsCount); for (var i = 0; i < bidsCount; i++) { - var bidReqeust = tlReq[i]; - var callbackId = utils.getUniqueIdentifierStr(); - adloader.loadScript(buildTLCall(bidReqeust, callbackId)); + var bidRequest = tlReq[i]; + var callbackId = bidRequest.bidderRequestId; + adloader.loadScript(buildTLCall(bidRequest, callbackId)); //store a reference to the bidRequest from the callback id - bidmanager.pbCallbackMap[callbackId] = bidReqeust; + //bidmanager.pbCallbackMap[callbackId] = bidRequest; } } @@ -72,8 +72,9 @@ var TripleLiftAdapter = function TripleLiftAdapter() { //expose the callback to the global object: pbjs.TLCB = function(tlResponseObj) { if (tlResponseObj && tlResponseObj.callback_id) { - var bidObj = bidmanager.pbCallbackMap[tlResponseObj.callback_id], - placementCode = bidObj.placementCode; + //var bidObj = bidmanager.pbCallbackMap[tlResponseObj.callback_id], + var bidObj = pbjs._bidsRequested.find(bidSet => bidSet.bidderRequestId === tlResponseObj.callback_id).bids.reduce((a, b) => b); + var placementCode = bidObj.placementCode; // @if NODE_ENV='debug' utils.logMessage('JSONP callback function called for inventory code: ' + bidObj.params.inventoryCode); diff --git a/src/bidfactory.js b/src/bidfactory.js index 639cbc966b6..d0ef73209de 100644 --- a/src/bidfactory.js +++ b/src/bidfactory.js @@ -49,6 +49,6 @@ function Bid(statusCode) { } // Bid factory function. -exports.createBid = function (statusCde) { - return new Bid(statusCde); +exports.createBid = function (statusCode) { + return new Bid(statusCode); }; diff --git a/src/bidmanager.js b/src/bidmanager.js index c1eca1cb5b4..7e053b83f60 100644 --- a/src/bidmanager.js +++ b/src/bidmanager.js @@ -1,209 +1,123 @@ +import { uniques } from './utils'; + var CONSTANTS = require('./constants.json'); var utils = require('./utils.js'); var events = require('./events'); var objectType_function = 'function'; -var objectType_undefined = 'undefined'; var externalCallbackByAdUnitArr = []; var externalCallbackArr = []; var externalOneTimeCallback = null; -var biddersByPlacementMap = {}; - -var pbCallbackMap = {}; -exports.pbCallbackMap = pbCallbackMap; - -var pbBidResponseByPlacement = {}; -exports.pbBidResponseByPlacement = pbBidResponseByPlacement; - -//this is used to look up the bid by bid ID later -var _adResponsesByBidderId = {}; -exports._adResponsesByBidderId = _adResponsesByBidderId; - -var bidResponseReceivedCount = {}; -exports.bidResponseReceivedCount = bidResponseReceivedCount; - -var expectedBidsCount = {}; -var _allBidsAvailable = false; -var _callbackExecuted = false; var _granularity = CONSTANTS.GRANULARITY_OPTIONS.MEDIUM; var defaultBidderSettingsMap = {}; -var bidderStartTimes = {}; - -exports.getPlacementIdByCBIdentifer = function (id) { - return pbCallbackMap[id]; -}; - -exports.getBidResponseByAdUnit = function () { - return pbBidResponseByPlacement; - -}; - -exports.clearAllBidResponses = function () { - _allBidsAvailable = false; - _callbackExecuted = false; - - //init bid response received count - initbidResponseReceivedCount(); - - //init expected bids count - initExpectedBidsCount(); - //clear the callback handler flag - externalCallbackArr.called = false; - - for (var prop in this.pbBidResponseByPlacement) { - delete this.pbBidResponseByPlacement[prop]; - } -}; +const _lgPriceCap = 5.00; +const _mgPriceCap = 20.00; +const _hgPriceCap = 20.00; /** * Returns a list of bidders that we haven't received a response yet * @return {array} [description] */ exports.getTimedOutBidders = function () { - return utils._map(bidResponseReceivedCount, function (count, bidderCode) { - if (count === 0) { - return bidderCode; - } - }); + return pbjs._bidsRequested + .map(getBidderCodes) + .filter(uniques) + .filter(bidder => pbjs._bidsReceived + .map(getBidders) + .filter(uniques) + .indexOf(bidder) < 0); }; -function initbidResponseReceivedCount() { +function timestamp() { return new Date().getTime(); } - //bidResponseReceivedCount = {}; - for (var prop in bidResponseReceivedCount) { - delete bidResponseReceivedCount[prop]; - } +function getBidderCodes(bidSet) { + return bidSet.bidderCode; +} - for (var i = 0; i < pbjs.adUnits.length; i++) { - var bids = pbjs.adUnits[i].bids; - for (var j = 0; j < bids.length; j++) { - var bidder = bids[j].bidder; - bidResponseReceivedCount[bidder] = 0; - } - } +function getBidders(bid) { + return bid.bidder; } -exports.increaseBidResponseReceivedCount = function (bidderCode) { - increaseBidResponseReceivedCount(bidderCode); -}; +function bidsBackAdUnit(adUnitCode) { + const requested = pbjs.adUnits.find(unit => unit.code === adUnitCode).bids.length; + const received = pbjs._bidsReceived.filter(bid => bid.adUnitCode === adUnitCode).length; + return requested === received; +} -function increaseBidResponseReceivedCount(bidderCode) { - if (typeof bidResponseReceivedCount[bidderCode] === objectType_undefined) { - bidResponseReceivedCount[bidderCode] = 1; - } else { - bidResponseReceivedCount[bidderCode]++; - } +function add(a, b) { + return a + b; } -function initExpectedBidsCount() { - expectedBidsCount = {}; +function bidsBackAll() { + const requested = pbjs._bidsRequested.map(bidSet => bidSet.bids.length).reduce(add); + const received = pbjs._bidsReceived.length; + return requested === received; } -exports.setExpectedBidsCount = function (bidderCode, count) { - expectedBidsCount[bidderCode] = count; +exports.bidsBackAll = function() { + return bidsBackAll(); }; -function getExpectedBidsCount(bidderCode) { - return expectedBidsCount[bidderCode]; +function getBidSetForBidder(bidder) { + return pbjs._bidsRequested.find(bidSet => bidSet.bidderCode === bidder); } -exports.getExpectedBidsCount = getExpectedBidsCount; - /* - * This function should be called to by the BidderObject to register a new bid is in + * This function should be called to by the bidder adapter to register a bid response */ exports.addBidResponse = function (adUnitCode, bid) { - var bidResponseObj = {}; - if (bid) { - - //record bid request and resposne time - bid.requestTimestamp = bidderStartTimes[bid.bidderCode]; - bid.responseTimestamp = new Date().getTime(); + Object.assign(bid, { + responseTimestamp: timestamp(), + requestTimestamp: getBidSetForBidder(bid.bidderCode).start, + cpm: bid.cpm || 0, + bidder: bid.bidderCode, + adUnitCode + }); bid.timeToRespond = bid.responseTimestamp - bid.requestTimestamp; - //increment the bid count - increaseBidResponseReceivedCount(bid.bidderCode); - - //get price settings here - if (bid.getStatusCode() === 2) { - bid.cpm = 0; - } - - // alias the bidderCode to bidder; - // NOTE: this is to match documentation - // on custom k-v targeting - bid.bidder = bid.bidderCode; - //emit the bidAdjustment event before bidResponse, so bid response has the adjusted bid value events.emit(CONSTANTS.EVENTS.BID_ADJUSTMENT, bid); //emit the bidResponse event events.emit(CONSTANTS.EVENTS.BID_RESPONSE, adUnitCode, bid); - var priceStringsObj = utils.getPriceBucketString(bid.cpm, bid.height, bid.width); - //append price strings + const priceStringsObj = getPriceBucketString(bid.cpm, bid.height, bid.width); bid.pbLg = priceStringsObj.low; bid.pbMg = priceStringsObj.med; bid.pbHg = priceStringsObj.high; bid.pbAg = priceStringsObj.auto; - - //put adUnitCode into bid - bid.adUnitCode = adUnitCode; + bid.pbDg = priceStringsObj.dense; //if there is any key value pairs to map do here var keyValues = {}; if (bid.bidderCode && bid.cpm !== 0) { - keyValues = this.getKeyValueTargetingPairs(bid.bidderCode, bid); + keyValues = getKeyValueTargetingPairs(bid.bidderCode, bid); bid.adserverTargeting = keyValues; } - //store a reference to the bidResponse by adId - if (bid.adId) { - _adResponsesByBidderId[bid.adId] = bid; - } - - //store by placement ID - if (adUnitCode && pbBidResponseByPlacement[adUnitCode] && !utils.isEmpty(pbBidResponseByPlacement[adUnitCode])) { - //update bid response object - bidResponseObj = pbBidResponseByPlacement[adUnitCode]; - - //bidResponseObj.status = statusCode; - bidResponseObj.bids.push(bid); - - //increment bid response by placement - bidResponseObj.bidsReceivedCount++; - - } else { - //should never reach this code - utils.logError('Internal error in bidmanager.addBidResponse. Params: ' + adUnitCode + ' & ' + bid); - } - - } else { - //create an empty bid bid response object - bidResponseObj = this.createEmptyBidResponseObj(); + pbjs._bidsReceived.push(bid); } - //store the bidResponse in a map - pbBidResponseByPlacement[adUnitCode] = bidResponseObj; + if (bidsBackAdUnit(bid.adUnitCode)) { + triggerAdUnitCallbacks(bid.adUnitCode); + } - this.checkIfAllBidsAreIn(adUnitCode); + if (bidsBackAll()) { + this.executeCallback(); + } - //TODO: check if all bids are in -}; + if (bid.timeToRespond > pbjs.bidderTimeout) { -exports.createEmptyBidResponseObj = function () { - return { - bids: [], - allBidsAvailable: false, - bidsReceivedCount: 0 - }; + events.emit(CONSTANTS.EVENTS.BID_TIMEOUT, this.getTimedOutBidders()); + this.executeCallback(); + } }; -exports.getKeyValueTargetingPairs = function (bidderCode, custBidObj) { +function getKeyValueTargetingPairs(bidderCode, custBidObj) { var keyValues = {}; var bidder_settings = pbjs.bidderSettings || {}; @@ -227,6 +141,8 @@ exports.getKeyValueTargetingPairs = function (bidderCode, custBidObj) { val: function (bidResponse) { if (_granularity === CONSTANTS.GRANULARITY_OPTIONS.AUTO) { return bidResponse.pbAg; + } else if (_granularity === CONSTANTS.GRANULARITY_OPTIONS.DENSE) { + return bidResponse.pbDg; } else if (_granularity === CONSTANTS.GRANULARITY_OPTIONS.LOW) { return bidResponse.pbLg; } else if (_granularity === CONSTANTS.GRANULARITY_OPTIONS.MEDIUM) { @@ -255,13 +171,17 @@ exports.getKeyValueTargetingPairs = function (bidderCode, custBidObj) { custBidObj.alwaysUseBid = bidder_settings[bidderCode].alwaysUseBid; } - //2) set keys from standard setting. NOTE: this API doesn't seeem to be in use by any Adapter currently + //2) set keys from standard setting. NOTE: this API doesn't seem to be in use by any Adapter else if (defaultBidderSettingsMap[bidderCode]) { setKeys(keyValues, defaultBidderSettingsMap[bidderCode], custBidObj); custBidObj.alwaysUseBid = defaultBidderSettingsMap[bidderCode].alwaysUseBid; } return keyValues; +} + +exports.getKeyValueTargetingPairs = function() { + return getKeyValueTargetingPairs(...arguments); }; function setKeys(keyValues, bidderSettings, custBidObj) { @@ -305,45 +225,17 @@ exports.registerDefaultBidderSetting = function (bidderCode, defaultSetting) { defaultBidderSettingsMap[bidderCode] = defaultSetting; }; -exports.registerBidRequestTime = function (bidderCode, time) { - bidderStartTimes[bidderCode] = time; -}; - exports.executeCallback = function () { - var params = []; - - //this pbjs.registerBidCallbackHandler will be deprecated soon - if (typeof pbjs.registerBidCallbackHandler === objectType_function && !_callbackExecuted) { - try { - pbjs.registerBidCallbackHandler(); - _callbackExecuted = true; - } catch (e) { - _callbackExecuted = true; - utils.logError('Exception trying to execute callback handler registered : ' + e.message); - } - } - - //trigger allBidsBack handler - //todo: get args if (externalCallbackArr.called !== true) { - processCallbacks(externalCallbackArr, params); + processCallbacks(externalCallbackArr); externalCallbackArr.called = true; } //execute one time callback if (externalOneTimeCallback) { - params = []; - var responseObj = pbjs.getBidResponses(); - params.push(responseObj); - - processCallbacks(externalOneTimeCallback, params); + processCallbacks([externalOneTimeCallback]); externalOneTimeCallback = null; } - -}; - -exports.allBidsBack = function () { - return _allBidsAvailable; }; function triggerAdUnitCallbacks(adUnitCode) { @@ -352,84 +244,14 @@ function triggerAdUnitCallbacks(adUnitCode) { processCallbacks(externalCallbackByAdUnitArr, params); } -function processCallbacks(callbackQueue, params) { +function processCallbacks(callbackQueue) { var i; if (utils.isArray(callbackQueue)) { for (i = 0; i < callbackQueue.length; i++) { var func = callbackQueue[i]; - callFunction(func, params); - } - } else { - callFunction(callbackQueue, params); - } - -} - -function callFunction(func, args) { - if (typeof func === 'function') { - try { - func.apply(pbjs, args); - - //func.executed = true; + func.call(pbjs, pbjs._bidsReceived); } - catch (e) { - utils.logError('Error executing callback function: ' + e.message); - } - } -} - -function checkBidsBackByAdUnit(adUnitCode) { - for (var i = 0; i < pbjs.adUnits.length; i++) { - var adUnit = pbjs.adUnits[i]; - if (adUnit.code === adUnitCode) { - var bidsBack = pbBidResponseByPlacement[adUnitCode].bidsReceivedCount; - - //all bids back for ad unit - if (bidsBack === adUnit.bids.length) { - triggerAdUnitCallbacks(adUnitCode); - - } - } - } -} - -exports.setBidderMap = function (bidderMap) { - biddersByPlacementMap = bidderMap; -}; - -/* - * This method checks if all bids have a response (bid, no bid, timeout) and will execute callback method if all bids are in - * TODO: Need to track bids by placement as well - */ - -exports.checkIfAllBidsAreIn = function (adUnitCode) { - - _allBidsAvailable = checkAllBidsResponseReceived(); - - //check by ad units - checkBidsBackByAdUnit(adUnitCode); - - if (_allBidsAvailable) { - //execute our calback method if it exists && pbjs.initAdserverSet !== true - this.executeCallback(); } -}; - -// check all bids response received by bidder -function checkAllBidsResponseReceived() { - var available = true; - - utils._each(bidResponseReceivedCount, function (count, bidderCode) { - var expectedCount = getExpectedBidsCount(bidderCode); - - // expectedCount should be set in the adapter, or it will be set - // after we call adapter.callBids() - if ((typeof expectedCount === objectType_undefined) || (count < expectedCount)) { - available = false; - } - }); - - return available; } /** @@ -472,3 +294,73 @@ function adjustBids(bid) { bid.cpm = bidPriceAdjusted; } } + +function getPriceBucketString(cpm) { + var cpmFloat = 0; + var returnObj = { + low: '', + med: '', + high: '', + auto: '', + dense: '' + }; + try { + cpmFloat = parseFloat(cpm); + if (cpmFloat) { + //round to closest .5 + if (cpmFloat > _lgPriceCap) { + returnObj.low = _lgPriceCap.toFixed(2); + } else { + returnObj.low = (Math.floor(cpm * 2) / 2).toFixed(2); + } + + //round to closest .1 + if (cpmFloat > _mgPriceCap) { + returnObj.med = _mgPriceCap.toFixed(2); + } else { + returnObj.med = (Math.floor(cpm * 10) / 10).toFixed(2); + } + + //round to closest .01 + if (cpmFloat > _hgPriceCap) { + returnObj.high = _hgPriceCap.toFixed(2); + } else { + returnObj.high = (Math.floor(cpm * 100) / 100).toFixed(2); + } + + // round auto default sliding scale + if (cpmFloat <= 5) { + // round to closest .05 + returnObj.auto = (Math.floor(cpm * 20) / 20).toFixed(2); + } else if (cpmFloat <= 10) { + // round to closest .10 + returnObj.auto = (Math.floor(cpm * 10) / 10).toFixed(2); + } else if (cpmFloat <= 20) { + // round to closest .50 + returnObj.auto = (Math.floor(cpm * 2) / 2).toFixed(2); + } else { + // cap at 20.00 + returnObj.auto = '20.00'; + } + + // dense mode + if (cpmFloat <= 3) { + // round to closest .01 + returnObj.dense = (Math.floor(cpm * 100) / 100).toFixed(2); + } else if (cpmFloat <= 8) { + // round to closest .05 + returnObj.dense = (Math.floor(cpm * 20) / 20).toFixed(2); + } else if (cpmFloat <= 20) { + // round to closest .50 + returnObj.dense = (Math.floor(cpm * 2) / 2).toFixed(2); + } else { + // cap at 20.00 + returnObj.dense = '20.00'; + } + } + } catch (e) { + this.logError('Exception parsing CPM :' + e.message); + } + + return returnObj; +} diff --git a/src/constants.json b/src/constants.json index d6a7c008c5e..caabf9eb7e3 100644 --- a/src/constants.json +++ b/src/constants.json @@ -41,7 +41,8 @@ "LOW": "low", "MEDIUM": "medium", "HIGH": "high", - "AUTO": "auto" + "AUTO": "auto", + "DENSE": "dense" }, "TARGETING_KEYS": [ "hb_bidder", diff --git a/src/polyfills.js b/src/polyfills.js deleted file mode 100644 index 56288172038..00000000000 --- a/src/polyfills.js +++ /dev/null @@ -1,74 +0,0 @@ -/** - * returns index of search element in array - * used by `utils` when Array.prototype.indexOf is unavailable (e.g. < IE9) - * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf#Polyfill - * - * @param searchElement - * @param fromIndex - * @returns {*} - */ -exports.indexOf = function (searchElement, fromIndex) { - - var len; - var k; - - // 1. Let o be the result of calling ToObject passing - // the this value as the argument. - if (this === null) { - throw new TypeError('"this" is null or not defined'); - } - - var o = Object(this); - - // 2. Let lenValue be the result of calling the Get - // internal method of o with the argument "length". - // 3. Let len be ToUint32(lenValue). - - len = o.length >>> 0; - - // 4. If len is 0, return -1. - if (len === 0) { - return -1; - } - - // 5. If argument fromIndex was passed let n be - // ToInteger(fromIndex); else let n be 0. - var n = +fromIndex || 0; - - if (Math.abs(n) === Infinity) { - n = 0; - } - - // 6. If n >= len, return -1. - if (n >= len) { - return -1; - } - - // 7. If n >= 0, then Let k be n. - // 8. Else, n<0, Let k be len - abs(n). - // If k is less than 0, then let k be 0. - k = Math.max(n >= 0 ? n : len - Math.abs(n), 0); - - // 9. Repeat, while k < len - while (k < len) { - // a. Let Pk be ToString(k). - // This is implicit for LHS operands of the in operator - // b. Let kPresent be the result of calling the - // HasProperty internal method of o with argument Pk. - // This step can be combined with c - // c. If kPresent is true, then - // i. Let elementK be the result of calling the Get - // internal method of o with the argument ToString(k). - // ii. Let same be the result of applying the - // Strict Equality Comparison Algorithm to - // searchElement and elementK. - // iii. If same is true, return k. - if (k in o && o[k] === searchElement) { - return k; - } - - k++; - } - - return -1; -}; diff --git a/src/prebid.js b/src/prebid.js index 38ac786eeed..72c10d89539 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -1,5 +1,7 @@ /** @module pbjs */ +import { flatten, uniques, getKeys } from './utils'; + // if pbjs already exists in global document scope, use it, if not, create the object window.pbjs = (window.pbjs || {}); window.pbjs.que = window.pbjs.que || []; @@ -18,15 +20,9 @@ var events = require('./events'); var objectType_function = 'function'; var objectType_undefined = 'undefined'; var objectType_object = 'object'; -var objectType_string = 'string'; var BID_WON = CONSTANTS.EVENTS.BID_WON; var BID_TIMEOUT = CONSTANTS.EVENTS.BID_TIMEOUT; -var pb_preBidders = []; -var pb_placements = []; -var pb_bidderMap = {}; -var pb_targetingMap = {}; -var pb_keyHistoryMap = {}; var pb_bidsTimedOut = false; var pb_sendAllBids = false; @@ -36,8 +32,11 @@ var eventValidators = { /* Public vars */ +pbjs._bidsRequested = []; +pbjs._bidsReceived = []; + //default timeout for all bids -pbjs.bidderTimeout = pbjs.bidderTimeout || 3000; +pbjs.bidderTimeout = pbjs.bidderTimeout || 2000; pbjs.logging = pbjs.logging || false; //let the world know we are loaded @@ -80,53 +79,6 @@ function processQue() { } } -/* - * Main method entry point method - */ -function init(timeout, adUnitCodeArr) { - var cbTimeout = 0; - if (typeof timeout === objectType_undefined || timeout === null) { - cbTimeout = pbjs.bidderTimeout; - } else { - cbTimeout = timeout; - } - - if (!isValidAdUnitSetting()) { - utils.logMessage('No adUnits configured. No bids requested.'); - return; - } - - //set timeout for all bids - setTimeout(bidmanager.executeCallback, cbTimeout); - - //parse settings into internal vars - if (adUnitCodeArr && utils.isArray(adUnitCodeArr)) { - for (var k = 0; k < adUnitCodeArr.length; k++) { - for (var i = 0; i < pbjs.adUnits.length; i++) { - if (pbjs.adUnits[i].code === adUnitCodeArr[k]) { - pb_placements.push(pbjs.adUnits[i]); - } - } - } - - loadPreBidders(); - sortAndCallBids(); - } else { - pb_placements = pbjs.adUnits; - - //Aggregrate prebidders by their codes - loadPreBidders(); - - //sort and call // default no sort - sortAndCallBids(); - } -} - -function isValidAdUnitSetting() { - return !!(pbjs.adUnits && pbjs.adUnits.length !== 0); - -} - function timeOutBidders() { if (!pb_bidsTimedOut) { pb_bidsTimedOut = true; @@ -135,227 +87,73 @@ function timeOutBidders() { } } -function sortAndCallBids(sortFunc) { - //Translate the bidder map into array so we can sort later if wanted - var pbArr = utils._map(pb_bidderMap, function (v) { - return v; - }); - - if (typeof sortFunc === objectType_function) { - pbArr.sort(sortFunc); - } - - adaptermanager.callBids(pbArr); -} - -function loadPreBidders() { - - for (var i = 0; i < pb_placements.length; i++) { - var bids = pb_placements[i][CONSTANTS.JSON_MAPPING.PL_BIDS]; - var placementCode = pb_placements[i][CONSTANTS.JSON_MAPPING.PL_CODE]; - storeBidRequestByBidder(pb_placements[i][CONSTANTS.JSON_MAPPING.PL_CODE], pb_placements[i][CONSTANTS.JSON_MAPPING.PL_SIZE], bids); - - //store pending response by placement - bidmanager.addBidResponse(placementCode); - } +function checkDefinedPlacement(id) { + var placementCodes = pbjs._bidsRequested.map(bidSet => bidSet.bids.map(bid => bid.placementCode)) + .reduce(flatten) + .filter(uniques); - for (i = 0; i < pb_preBidders.length; i++) { - pb_preBidders[i].loadPreBid(); + if (!utils.contains(placementCodes, id)) { + utils.logError('The "' + id + '" placement is not defined.'); + return; } - //send a reference to bidmanager - bidmanager.setBidderMap(pb_bidderMap); + return true; } -function storeBidRequestByBidder(placementCode, sizes, bids) { - for (var i = 0; i < bids.length; i++) { - - var currentBid = bids[i]; - currentBid.placementCode = placementCode; - currentBid.sizes = sizes; - - //look up bidder on map - var bidderName = bids[i][CONSTANTS.JSON_MAPPING.BD_BIDDER]; - var bidderObj = pb_bidderMap[bidderName]; - if (typeof bidderObj === objectType_undefined) { - //bid not found - var partnerBids = { - bidderCode: bidderName, - bids: [] +function getWinningBidTargeting() { + const presets = (function getPresetTargeting() { + return window.googletag.pubads().getSlots().map(slot => { + return { + [slot.getAdUnitPath()]: slot.getTargetingKeys().map(key => { + return { [key]: slot.getTargeting(key) }; + }) }; - partnerBids.bids.push(currentBid); - - //put bidder on map with bids - pb_bidderMap[bidderName] = partnerBids; - } else { - bidderObj.bids.push(currentBid); - } - - } -} - -function getWinningBid(bidArray) { - if (bidArray && bidArray.length !== 0) { - bidArray.sort(function (a, b) { - //put the highest CPM first - return b.cpm - a.cpm; }); + })(); + + const winners = pbjs._bidsReceived.map(bid => bid.adUnitCode) + .filter(uniques) + .map(adUnitCode => pbjs._bidsReceived + .filter(bid => bid.adUnitCode === adUnitCode ? bid : null) + .reduce(getHighestCpm, + { + adUnitCode: adUnitCode, + cpm: 0, + adserverTargeting: {} + })); + + return winners.map(winner => { + return { + [winner.adUnitCode]: Object.keys(winner.adserverTargeting, key => key) + .map(key => { + return { [key.substring(0, 20)]: [winner.adserverTargeting[key]] }; + }) + }; + }).concat(presets); - // The first item has the highest cpm - // If winning bid CPM === 0 - we need to indicate no targeting should be set - if (bidArray[0].cpm === 0 ) { - return null; - } - return bidArray[0].bid; - } -} - -function setGPTAsyncTargeting(code, slot) { - //get the targeting that is already configured - const keyStrings = getTargetingfromGPTIdentifier(slot); - //this is a temp fix until we merge the refactor - var bids = pbjs.getBidResponses(slot.getAdUnitPath()); - if(bids.bids.length === 0) { - bids = pbjs.getBidResponses(slot.getSlotElementId()); - } - - //copy keyStrings into pb_keyHistoryMap by code - if (!pb_keyHistoryMap[code]) { - pb_keyHistoryMap[code] = keyStrings; - } else { - utils.extend(pb_keyHistoryMap[code], keyStrings); - } - - if (pb_sendAllBids && bids && bids.bids && bids.bids.length) { - utils.extend(pb_keyHistoryMap[code], getTargetingKeysAsBidder(bids.bids)); - } - - utils._each(pb_keyHistoryMap[code], function (value, key) { - //since DFP doesn't support deleting a single key, we will set all to empty string - //This is "clear" for that key - slot.setTargeting(key, ''); - - //utils.logMessage('Attempting to clear the key : ' + key + ' to empty string for code: ' + code); - }); - - for (var key in keyStrings) { - if (keyStrings.hasOwnProperty(key)) { - try { - utils.logMessage('Attempting to set key value for slot: ' + slot.getSlotElementId() + ' key: ' + key + ' value: ' + encodeURIComponent(keyStrings[key])); - slot.setTargeting(key, keyStrings[key]); - } catch (e) { - utils.logMessage('Problem setting key value pairs in slot: ' + e.message); - } - } + function getHighestCpm(previous, current) { + return previous.cpm < current.cpm ? current : previous; } } -/* - * This function returns the object map of placements or - * if placement code is specified return just 1 placement bids - */ -function getBidResponsesByAdUnit(adunitCode) { - var returnObj = {}; - if (adunitCode) { - returnObj = bidmanager.pbBidResponseByPlacement[adunitCode]; - return returnObj; - } else { - return bidmanager.pbBidResponseByPlacement; - } -} - -/* - * Copies bids into a bidArray response - */ -function buildBidResponse(bidArray) { - var bidResponseArray = []; - var adUnitCode = ''; - - //temp array to hold auction for bids - var bidArrayTargeting = []; - var bidClone = {}; - if (bidArray && bidArray[0] && bidArray[0].adUnitCode) { - // init the pb_targetingMap for the adUnitCode - adUnitCode = bidArray[0] && bidArray[0].adUnitCode; - pb_targetingMap[adUnitCode] = {}; - for (var i = 0; i < bidArray.length; i++) { - var bid = bidArray[i]; - - //clone by json parse. This also gets rid of unwanted function properties - bidClone = getCloneBid(bid); - - if (bid.alwaysUseBid && bidClone.adserverTargeting) { // add the bid if alwaysUse and bid has returned - // push key into targeting - pb_targetingMap[bidClone.adUnitCode] = utils.extend(pb_targetingMap[bidClone.adUnitCode], bidClone.adserverTargeting); - } - - if (bid.cpm && bid.cpm > 0) { - //else put into auction array if cpm > 0 - bidArrayTargeting.push({ - cpm: bid.cpm, - bid: bid - }); - } - //put all bids into bidArray by default - bidResponseArray.push(bidClone); - } - } +function getBidLandscapeTargeting() { + const standardKeys = CONSTANTS.TARGETING_KEYS; - // push the winning bid into targeting map - if (adUnitCode && bidArrayTargeting.length !== 0) { - var winningBid = getWinningBid(bidArrayTargeting); - if (winningBid) { - var keyValues = winningBid.adserverTargeting; - pb_targetingMap[adUnitCode] = utils.extend(pb_targetingMap[adUnitCode], keyValues); - } else { - utils.logWarn('No winning bids available.'); + return pbjs._bidsReceived.map(bid => { + if (bid.adserverTargeting) { + return { + [bid.adUnitCode]: standardKeys.map(key => { + return { + [`${key}_${bid.bidderCode}`.substring(0, 20)]: [bid.adserverTargeting[key]] + }; + }) + }; } - } - - return bidResponseArray; -} - -function getCloneBid(bid) { - var bidClone = {}; - - //clone by json parse. This also gets rid of unwanted function properties - if (bid) { - var jsonBid = JSON.stringify(bid); - bidClone = JSON.parse(jsonBid); - - //clean up bid clone - delete bidClone.pbLg; - delete bidClone.pbMg; - delete bidClone.pbHg; - } - - return bidClone; + }).filter(bid => bid); // removes empty elements in array } -function resetBids() { - bidmanager.clearAllBidResponses(); - pb_bidderMap = {}; - pb_placements = []; - pb_targetingMap = {}; - pb_bidsTimedOut = false; -} - -function requestAllBids(timeout) { - resetBids(); - init(timeout); -} - -function checkDefinedPlacement(id) { - var placementCodes = utils._map(pb_placements, function (placement) { - return placement.code; - }); - - if (!utils.contains(placementCodes, id)) { - utils.logError('The "' + id + '" placement is not defined.'); - return; - } - - return true; +function getAllTargeting() { + return getWinningBidTargeting().concat(pb_sendAllBids ? getBidLandscapeTargeting() : []); } ////////////////////////////////// @@ -363,6 +161,7 @@ function checkDefinedPlacement(id) { // Start Public APIs // // // ////////////////////////////////// + /** * This function returns the query string targeting parameters available at this moment for a given ad unit. Note that some bidder's response may not have been received if you call this function too quickly after the requests are sent. * @param {string} [adunitCode] adUnitCode to get the bid responses for @@ -371,6 +170,7 @@ function checkDefinedPlacement(id) { */ pbjs.getAdserverTargetingForAdUnitCodeStr = function (adunitCode) { utils.logInfo('Invoking pbjs.getAdserverTargetingForAdUnitCodeStr', arguments); + // call to retrieve bids array if (adunitCode) { var res = pbjs.getAdserverTargetingForAdUnitCode(adunitCode); @@ -379,188 +179,109 @@ pbjs.getAdserverTargetingForAdUnitCodeStr = function (adunitCode) { utils.logMessage('Need to call getAdserverTargetingForAdUnitCodeStr with adunitCode'); } }; + /** * This function returns the query string targeting parameters available at this moment for a given ad unit. Note that some bidder's response may not have been received if you call this function too quickly after the requests are sent. * @param {string} [adunitCode] adUnitCode to get the bid responses for * @alias module:pbjs.getAdserverTargetingForAdUnitCode * @return {object} returnObj return bids */ -pbjs.getAdserverTargetingForAdUnitCode = function (adunitCode) { - utils.logInfo('Invoking pbjs.getAdserverTargetingForAdUnitCode', arguments); - // call to populate pb_targetingMap - pbjs.getBidResponses(adunitCode); - if (adunitCode) { - return pb_targetingMap[adunitCode]; - } +pbjs.getAdserverTargetingForAdUnitCode = function (adUnitCode) { + utils.logInfo('Invoking pbjs.getAdserverTargetingForAdUnitCode', arguments); - return pb_targetingMap; + return getAllTargeting().filter(targeting => getKeys(targeting)[0] === adUnitCode) + .map(targeting => { + return { + [Object.keys(targeting)[0]]: targeting[Object.keys(targeting)[0]] + .map(target => { + return { + [Object.keys(target)[0]]: target[Object.keys(target)[0]].join(', ') + }; + }).reduce((p, c) => Object.assign(c, p), {}) + }; + }) + .reduce(function (accumulator, targeting) { + var key = Object.keys(targeting)[0]; + accumulator[key] = Object.assign({}, accumulator[key], targeting[key]); + return accumulator; + }, {})[adUnitCode]; }; + /** * returns all ad server targeting for all ad units * @return {object} Map of adUnitCodes and targeting values [] * @alias module:pbjs.getAdserverTargeting */ + pbjs.getAdserverTargeting = function () { utils.logInfo('Invoking pbjs.getAdserverTargeting', arguments); - return pbjs.getAdserverTargetingForAdUnitCode(); + return getAllTargeting() + .map(targeting => { + return { + [Object.keys(targeting)[0]]: targeting[Object.keys(targeting)[0]] + .map(target => { + return { + [Object.keys(target)[0]]: target[Object.keys(target)[0]].join(', ') + }; + }).reduce((p, c) => Object.assign(c, p), {}) + }; + }) + .reduce(function (accumulator, targeting) { + var key = Object.keys(targeting)[0]; + accumulator[key] = Object.assign({}, accumulator[key], targeting[key]); + return accumulator; + }, {}); }; /** * This function returns the bid responses at the given moment. - * @param {string} [adunitCode] adunitCode adUnitCode to get the bid responses for * @alias module:pbjs.getBidResponses * @return {object} map | object that contains the bidResponses */ -pbjs.getBidResponses = function (adunitCode) { - utils.logInfo('Invoking pbjs.getBidResponses', arguments); - var response = {}; - var bidArray = []; - var returnObj = {}; - - if (adunitCode) { - response = getBidResponsesByAdUnit(adunitCode); - bidArray = []; - if (response && response.bids) { - bidArray = buildBidResponse(response.bids); - } - - returnObj = { - bids: bidArray - }; - - } else { - response = getBidResponsesByAdUnit(); - for (var adUnit in response) { - if (response.hasOwnProperty(adUnit)) { - if (response && response[adUnit] && response[adUnit].bids) { - bidArray = buildBidResponse(response[adUnit].bids); - } - returnObj[adUnit] = { - bids: bidArray - }; - - } - } - } - - return returnObj; +pbjs.getBidResponses = function () { + utils.logInfo('Invoking pbjs.getBidResponses', arguments); + return pbjs._bidsReceived.map(bid => bid.adUnitCode) + .filter(uniques).map(adUnitCode => pbjs._bidsReceived + .filter(bid => bid.adUnitCode === adUnitCode)) + .map(bids => { + return { + [bids[0].adUnitCode]: { bids: bids } + }; + }) + .reduce((a, b) => Object.assign(a, b), {}); }; + /** * Returns bidResponses for the specified adUnitCode * @param {String} adUnitCode adUnitCode * @alias module:pbjs.getBidResponsesForAdUnitCode * @return {Object} bidResponse object */ -pbjs.getBidResponsesForAdUnitCode = function (adUnitCode) { - utils.logInfo('Invoking pbjs.getBidResponsesForAdUnitCode', arguments); - return pbjs.getBidResponses(adUnitCode); -}; -/** - * Set query string targeting on adUnits specified. The logic for deciding query strings is described in the section Configure AdServer Targeting. Note that this function has to be called after all ad units on page are defined. - * @param {array} [codeArr] an array of adUnitodes to set targeting for. - * @alias module:pbjs.setTargetingForAdUnitsGPTAsync - */ -pbjs.setTargetingForAdUnitsGPTAsync = function (codeArr) { - utils.logInfo('Invoking pbjs.setTargetingForAdUnitsGPTAsync', arguments); - if (!window.googletag || !utils.isFn(window.googletag.pubads) || !utils.isFn(window.googletag.pubads().getSlots)) { - utils.logError('window.googletag is not defined on the page'); - return; - } - - //emit bid timeout event here - timeOutBidders(); - - var adUnitCodesArr = codeArr; - - if (typeof codeArr === objectType_string) { - adUnitCodesArr = [codeArr]; - } else if (typeof codeArr === objectType_object) { - adUnitCodesArr = codeArr; - } - - var placementBids = {}; - var i = 0; - var slots; - - if (adUnitCodesArr) { - for (i = 0; i < adUnitCodesArr.length; i++) { - var code = adUnitCodesArr[i]; - - //get all the slots from google tag - slots = window.googletag.pubads().getSlots(); - for (var k = 0; k < slots.length; k++) { - - if (slots[k].getSlotElementId() === code || slots[k].getAdUnitPath() === code) { - placementBids = getBidResponsesByAdUnit(code); - setGPTAsyncTargeting(code, slots[k]); - } - } - } - } else { - //get all the slots from google tag - slots = window.googletag.pubads().getSlots(); - for (i = 0; i < slots.length; i++) { - var adUnitCode = slots[i].getSlotElementId(); - if (adUnitCode) { - //placementBids = getBidsFromGTPIdentifier(slots[i]); - setGPTAsyncTargeting(adUnitCode, slots[i]); - } - } - } +pbjs.getBidResponsesForAdUnitCode = function (adUnitCode) { + const bids = pbjs._bidsReceived.filter(bid => bid.adUnitCode === adUnitCode); + return { + bids : bids + }; }; -/** - * Returns a string identifier (either DivId or adUnitPath) - * @param {[type]} slot [description] - * @return {[type]} [description] - */ -function getTargetingfromGPTIdentifier(slot) { - var targeting = null; - if (slot) { - //first get by elementId - targeting = pbjs.getAdserverTargetingForAdUnitCode(slot.getSlotElementId()); - - //if not available, try by adUnitPath - if (!targeting) { - targeting = pbjs.getAdserverTargetingForAdUnitCode(slot.getAdUnitPath()); - } - } - - return targeting; -} - -/** - * returns targeting keys with key name appended with the bidder code - * @param bidArray an array of current bid objects - */ -function getTargetingKeysAsBidder(bidArray) { - const standardKeys = CONSTANTS.TARGETING_KEYS; - let pairs = {}; - - // this assumes no key name collisions, which should not be possible, - // because bidder names are constrained by the adapter filename - // so uniqueness is enforced by the file system. - utils._each(bidArray, bid => { - if (bid.adserverTargeting) { - utils._each(standardKeys, key => { - pairs[`${key}_${bid.bidderCode}`] = bid.adserverTargeting[key]; - }); - } - }); - - return pairs; -} /** * Set query string targeting on all GPT ad units. * @alias module:pbjs.setTargetingForGPTAsync */ -pbjs.setTargetingForGPTAsync = function (codeArr) { +pbjs.setTargetingForGPTAsync = function () { + window.googletag.pubads().getSlots().forEach(slot => { + getAllTargeting() + .filter(targeting => Object.keys(targeting)[0] === slot.getAdUnitPath()) + .forEach(targeting => targeting[Object.keys(targeting)[0]].forEach(key => { + key[Object.keys(key)[0]].forEach(value => slot.setTargeting(Object.keys(key)[0], value)); + })); + }); + utils.logInfo('Invoking pbjs.setTargetingForGPTAsync', arguments); - pbjs.setTargetingForAdUnitsGPTAsync(codeArr); }; /** @@ -570,7 +291,7 @@ pbjs.setTargetingForGPTAsync = function (codeArr) { */ pbjs.allBidsAvailable = function () { utils.logInfo('Invoking pbjs.allBidsAvailable', arguments); - return bidmanager.allBidsBack(); + return bidmanager.bidsBackAll(); }; /** @@ -585,7 +306,7 @@ pbjs.renderAd = function (doc, id) { if (doc && id) { try { //lookup ad by ad Id - var adObject = bidmanager._adResponsesByBidderId[id]; + var adObject = pbjs._bidsReceived.find(bid => bid.adId === id); if (adObject) { //emit 'bid won' event here events.emit(BID_WON, adObject); @@ -631,32 +352,6 @@ pbjs.renderAd = function (doc, id) { }; -/** - * @deprecated - will be removed next release. Use pbjs.requestBids - */ -pbjs.requestBidsForAdUnit = function (adUnitCode) { - resetBids(); - init(adUnitCode); - -}; - -/** - * @deprecated - will be removed next release. Use pbjs.requestBids - */ -pbjs.requestBidsForAdUnits = function (adUnitsObj) { - if (!adUnitsObj || adUnitsObj.constructor !== Array) { - utils.logError('requestBidsForAdUnits must pass an array of adUnits'); - return; - } - - resetBids(); - var adUnitBackup = pbjs.adUnits.slice(0); - pbjs.adUnits = adUnitsObj; - init(); - pbjs.adUnits = adUnitBackup; - -}; - /** * Remove adUnit from the pbjs configuration * @param {String} adUnitCode the adUnitCode to remove @@ -674,45 +369,29 @@ pbjs.removeAdUnit = function (adUnitCode) { }; /** - * Request bids ad-hoc. This function does not add or remove adUnits already configured. - * @param {Object} requestObj - * @param {string[]} requestObj.adUnitCodes adUnit codes to request. Use this or requestObj.adUnits - * @param {object[]} requestObj.adUnits AdUnitObjects to request. Use this or requestObj.adUnitCodes - * @param {number} [requestObj.timeout] Timeout for requesting the bids specified in milliseconds - * @param {function} [requestObj.bidsBackHandler] Callback to execute when all the bid responses are back or the timeout hits. - * @alias module:pbjs.requestBids + * + * @param bidsBackHandler + * @param timeout */ -pbjs.requestBids = function (requestObj) { - utils.logInfo('Invoking pbjs.requestBids', arguments); - if (!requestObj) { - requestAllBids(); - } else { - var adUnitCodes = requestObj.adUnitCodes; - var adUnits = requestObj.adUnits; - var timeout = requestObj.timeout; - var bidsBackHandler = requestObj.bidsBackHandler; - var adUnitBackup = pbjs.adUnits.slice(0); - - if (typeof bidsBackHandler === objectType_function) { - bidmanager.addOneTimeCallback(bidsBackHandler); - } +pbjs.requestBids = function ({ bidsBackHandler, timeout }) { + const cbTimeout = timeout || pbjs.bidderTimeout; - if (adUnitCodes && utils.isArray(adUnitCodes)) { - resetBids(); - init(timeout, adUnitCodes); - - } else if (adUnits && utils.isArray(adUnits)) { - resetBids(); - pbjs.adUnits = adUnits; - init(timeout); - } else { - //request all ads - requestAllBids(timeout); - } + if (typeof bidsBackHandler === objectType_function) { + bidmanager.addOneTimeCallback(bidsBackHandler); + } - pbjs.adUnits = adUnitBackup; + utils.logInfo('Invoking pbjs.requestBids', arguments); + + // not sure of this logic + if (!pbjs.adUnits && pbjs.adUnits.length !== 0) { + utils.logMessage('No adUnits configured. No bids requested.'); + return; } + //set timeout for all bids + setTimeout(bidmanager.executeCallback, cbTimeout); + + adaptermanager.callBids(); }; /** @@ -822,37 +501,17 @@ pbjs.registerBidAdapter = function (bidderAdaptor, bidderCode) { } }; -/** - * - */ pbjs.bidsAvailableForAdapter = function (bidderCode) { utils.logInfo('Invoking pbjs.bidsAvailableForAdapter', arguments); - //TODO getAd - var bids = pb_bidderMap[bidderCode].bids; - - for (var i = 0; i < bids.length; i++) { - var adunitCode = bids[i].placementCode; - var responseObj = bidmanager.pbBidResponseByPlacement[adunitCode]; - - var bid = bidfactory.createBid(1); - - // bid.creative_id = adId; - bid.bidderCode = bidderCode; - bid.adUnitCode = adunitCode; - bid.bidder = bidderCode; - - // bid.cpm = responseCPM; - // bid.adUrl = jptResponseObj.result.ad; - // bid.width = jptResponseObj.result.width; - // bid.height = jptResponseObj.result.height; - // bid.dealId = jptResponseObj.result.deal_id; - - responseObj.bids.push(bid); - responseObj.bidsReceivedCount++; - bidmanager.pbBidResponseByPlacement[adunitCode] = responseObj; - } - bidmanager.increaseBidResponseReceivedCount(bidderCode); + pbjs._bidsRequested.find(bidderRequest => bidderRequest.bidderCode === bidderCode).bids + .map(bid => { + return Object.assign(bid, bidfactory.createBid(1), { + bidderCode, + adUnitCode: bid.placementCode + }); + }) + .map(bid => pbjs._bidsReceived.push(bid)); }; /** @@ -886,71 +545,6 @@ pbjs.loadScript = function (tagSrc, callback, useCache) { adloader.loadScript(tagSrc, callback, useCache); }; -/** - * This isn't ready yet - * return data for analytics - * @param {Function} [description] - * @return {[type]} [description] - - pbjs.getAnalyticsData = function(){ - var returnObj = {}; - var bidResponses = pbjs.getBidResponses(); - - //create return obj for all adUnits - for(var i=0;i 0) { + + return getKeys(targeting) + .map(key => `${key}=${encodeURIComponent(getValue(targeting, key))}`).join('&'); + } else { return ''; - for (var k in adServerTargeting) - if (adServerTargeting.hasOwnProperty(k)) - result += k + '=' + encodeURIComponent(adServerTargeting[k]) + '&'; - return result; + } }; //Copy all of the properties in the source objects over to the target object @@ -183,14 +179,14 @@ exports.logWarn = function (msg) { } }; -exports.logInfo = function(msg, args) { +exports.logInfo = function (msg, args) { if (debugTurnedOn() && hasConsoleLogger()) { if (infoLogger) { if (!args || args.length === 0) { args = ''; } - infoLogger('INFO: ' + msg + ((args === '') ? '' : ' : params : '), args); + infoLogger('INFO: ' + msg + ((args === '') ? '' : ' : params : '), args); } } }; @@ -263,66 +259,6 @@ var getParameterByName = function (name) { return decodeURIComponent(results[1].replace(/\+/g, ' ')); }; -exports.getPriceBucketString = function (cpm) { - var low = ''; - var med = ''; - var high = ''; - var auto = ''; - - var cpmFloat = 0; - var returnObj = { - low: low, - med: med, - high: high, - auto: auto - }; - try { - cpmFloat = parseFloat(cpm); - if (cpmFloat) { - //round to closest .5 - if (cpmFloat > _lgPriceCap) { - returnObj.low = _lgPriceCap.toFixed(2); - } else { - returnObj.low = (Math.floor(cpm * 2) / 2).toFixed(2); - } - - //round to closest .1 - if (cpmFloat > _mgPriceCap) { - returnObj.med = _mgPriceCap.toFixed(2); - } else { - returnObj.med = (Math.floor(cpm * 10) / 10).toFixed(2); - } - - //round to closest .01 - if (cpmFloat > _hgPriceCap) { - returnObj.high = _hgPriceCap.toFixed(2); - } else { - returnObj.high = (Math.floor(cpm * 100) / 100).toFixed(2); - } - - // round auto default sliding scale - if (cpmFloat <= 5) { - // round to closest .05 - returnObj.auto = (Math.floor(cpm * 20) / 20).toFixed(2); - } else if (cpmFloat <= 10) { - // round to closest .10 - returnObj.auto = (Math.floor(cpm * 10) / 10).toFixed(2); - } else if (cpmFloat <= 20) { - // round to closest .50 - returnObj.auto = (Math.floor(cpm * 2) / 2).toFixed(2); - } else { - // cap at 20.00 - returnObj.auto = '20.00'; - } - } - } catch (e) { - this.logError('Exception parsing CPM :' + e.message); - } - - return returnObj; - -}; - /** * This function validates paramaters. * @param {object[string]} paramObj [description] @@ -448,7 +384,8 @@ exports.indexOf = (function () { return Array.prototype.indexOf; } - return polyfills.indexOf; + // ie8 no longer supported + //return polyfills.indexOf; }()); /** @@ -518,3 +455,29 @@ exports.getIframeDocument = function (iframe) { return doc; }; + +export function uniques(value, index, arry) { + return arry.indexOf(value) === index; +} + +export function flatten(a, b) { + return a.concat(b); +} + +export function getBidRequest(id) { + return pbjs._bidsRequested.map(bidSet => bidSet.bids.find(bid => bid.bidId === id)).find(bid => bid); +} + +export function getKeys(obj) { + return Object.keys(obj); +} + +export function getValue(obj, key) { + return obj[key]; +} + +export function getBidderCodes() { + // this could memoize adUnits + return pbjs.adUnits.map(unit => unit.bids.map(bid => bid.bidder) + .reduce(flatten, [])).reduce(flatten).filter(uniques); +} diff --git a/test/fixtures/config.json b/test/fixtures/config.json index 5b842813923..da00ce5478b 100644 --- a/test/fixtures/config.json +++ b/test/fixtures/config.json @@ -5,7 +5,7 @@ "div-test-ad-2" ], "adUnitCodes": [ - "/123456/header-bid-tag-0", + "/19968336/header-bid-tag-0", "/123456/header-bid-tag-1", "/123456/header-bid-tag-2" ] diff --git a/test/fixtures/fixtures.js b/test/fixtures/fixtures.js new file mode 100644 index 00000000000..3799eab7208 --- /dev/null +++ b/test/fixtures/fixtures.js @@ -0,0 +1,1105 @@ +// jscs:disable + +export function getBidRequests() { + return [ + { + "bidderCode": "appnexus", + "requestId": "1863e370099523", + "bidderRequestId": "2946b569352ef2", + "bids": [ + { + "bidder": "appnexus", + "params": { + "placementId": "4799418", + "test": "me" + }, + "placementCode": "/19968336/header-bid-tag1", + "sizes": [ + [ + 728, + 90 + ], + [ + 970, + 90 + ] + ], + "bidId": "392b5a6b05d648", + "bidderRequestId": "2946b569352ef2", + "requestId": "1863e370099523", + "startTime": 1462918897462, + "status": 1 + }, + { + "bidder": "appnexus", + "params": { + "placementId": "4799418" + }, + "placementCode": "/19968336/header-bid-tag-0", + "sizes": [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + "bidId": "4dccdc37746135", + "bidderRequestId": "2946b569352ef2", + "requestId": "1863e370099523", + "startTime": 1462918897463, + "status": 1 + } + ], + "start": 1462918897460 + }, + { + "bidderCode": "pubmatic", + "requestId": "1863e370099523", + "bidderRequestId": "5e1525bae3eb11", + "bids": [ + { + "bidder": "pubmatic", + "params": { + "publisherId": 39741, + "adSlot": "39620189@300x250" + }, + "placementCode": "/19968336/header-bid-tag-0", + "sizes": [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + "bidId": "6d11aa2d5b3659", + "bidderRequestId": "5e1525bae3eb11", + "requestId": "1863e370099523" + } + ], + "start": 1462918897463 + }, + { + "bidderCode": "rubicon", + "requestId": "1863e370099523", + "bidderRequestId": "8778750ee15a77", + "bids": [ + { + "bidder": "rubicon", + "params": { + "accountId": "14062", + "siteId": "70608", + "zoneId": "335918", + "userId": "12346", + "keywords": [ + "a", + "b", + "c" + ], + "inventory": { + "rating": "5-star", + "prodtype": "tech" + }, + "visitor": { + "ucat": "new", + "search": "iphone" + }, + "sizes": [ + 15, + 10 + ] + }, + "placementCode": "/19968336/header-bid-tag-0", + "sizes": [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + "bidId": "96aff279720d39", + "bidderRequestId": "8778750ee15a77", + "requestId": "1863e370099523" + } + ], + "start": 1462918897474 + }, + { + "bidderCode": "triplelift", + "requestId": "1863e370099523", + "bidderRequestId": "107f5e6e98dcf09", + "bids": [ + { + "bidder": "triplelift", + "params": { + "inventoryCode": "sortable_all_right_sports" + }, + "placementCode": "/19968336/header-bid-tag-0", + "sizes": [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + "bidId": "1144e2f0de84363", + "bidderRequestId": "107f5e6e98dcf09", + "requestId": "1863e370099523", + "startTime": 1462918897477 + } + ], + "start": 1462918897475 + }, + { + "bidderCode": "brightcom", + "requestId": "1863e370099523", + "bidderRequestId": "12eeded736650b4", + "bids": [ + { + "bidder": "brightcom", + "params": { + "tagId": 16577 + }, + "placementCode": "/19968336/header-bid-tag-0", + "sizes": [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + "bidId": "135e89c039705da", + "bidderRequestId": "12eeded736650b4", + "requestId": "1863e370099523", + "status": 1 + } + ], + "start": 1462918897477 + }, + { + "bidderCode": "brealtime", + "requestId": "1863e370099523", + "bidderRequestId": "167c4d79b615948", + "bids": [ + { + "bidder": "brealtime", + "params": { + "placementId": "4799418" + }, + "placementCode": "/19968336/header-bid-tag-0", + "sizes": [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + "bidId": "17dd1d869bed44e", + "bidderRequestId": "167c4d79b615948", + "requestId": "1863e370099523", + "startTime": 1462918897480, + "status": 1 + } + ], + "start": 1462918897479 + }, + { + "bidderCode": "pagescience", + "requestId": "1863e370099523", + "bidderRequestId": "18bed198c172a69", + "bids": [ + { + "bidder": "pagescience", + "params": { + "placementId": "4799418" + }, + "placementCode": "/19968336/header-bid-tag-0", + "sizes": [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + "bidId": "192c8c1df0f5d1d", + "bidderRequestId": "18bed198c172a69", + "requestId": "1863e370099523", + "startTime": 1462918897481, + "status": 1 + } + ], + "start": 1462918897480 + }, + { + "bidderCode": "amazon", + "requestId": "1863e370099523", + "bidderRequestId": "20d0d30333715a7", + "bids": [ + { + "bidder": "amazon", + "params": { + "aId": 3080 + }, + "placementCode": "/19968336/header-bid-tag-0", + "sizes": [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + "bidId": "21ae8131ec04f6e", + "bidderRequestId": "20d0d30333715a7", + "requestId": "1863e370099523" + } + ], + "start": 1462918897482 + } + ]; +} + +export function getBidResponses() { + return [ + { + "bidderCode": "triplelift", + "width": 0, + "height": 0, + "statusMessage": "Bid available", + "adId": "222bb26f9e8bd", + "cpm": 0.112256, + "ad": "", + "responseTimestamp": 1462919239337, + "requestTimestamp": 1462919238936, + "bidder": "triplelift", + "adUnitCode": "/19968336/header-bid-tag-0", + "timeToRespond": 401, + "pbLg": "0.00", + "pbMg": "0.10", + "pbHg": "0.11", + "pbAg": "0.10", + "size": "0x0", + "adserverTargeting": { + "hb_bidder": "triplelift", + "hb_adid": "222bb26f9e8bd", + "hb_pb": "10.00", + "hb_size": "0x0", + "foobar": "0x0" + } + }, + { + "bidderCode": "appnexus", + "width": 300, + "height": 250, + "statusMessage": "Bid available", + "adId": "233bcbee889d46d", + "creative_id": 29681110, + "cpm": 10, + "adUrl": "http://lax1-ib.adnxs.com/ab?e=wqT_3QL8BKh8AgAAAwDWAAUBCMjAybkFEMLLiJWTu9PsVxjL84KE1tzG-kkgASotCQAAAQII4D8RAQcQAADgPxkJCQjwPyEJCQjgPykRCaAwuvekAji-B0C-B0gCUNbLkw5YweAnYABokUB4190DgAEBigEDVVNEkgUG8FKYAawCoAH6AagBAbABALgBAcABA8gBANABANgBAOABAPABAIoCOnVmKCdhJywgNDk0NDcyLCAxNDYyOTE5MjQwKTt1ZigncicsIDI5NjgxMTEwLDIeAPBskgLZASFmU21rZ0FpNjBJY0VFTmJMa3c0WUFDREI0Q2N3QURnQVFBUkl2Z2RRdXZla0FsZ0FZSk1IYUFCd0EzZ0RnQUVEaUFFRGtBRUJtQUVCb0FFQnFBRURzQUVBdVFFQUFBQUFBQURnUDhFQgkMTEFBNERfSkFRMkxMcEVUMU93XzJRFSggd1AtQUJBUFVCBSxASmdDaW9EVTJnV2dBZ0MxQWcBFgRDOQkIqERBQWdQSUFnUFFBZ1BZQWdQZ0FnRG9BZ0Q0QWdDQUF3RS6aAiUhV1FrbmI63AAcd2VBbklBUW8JXPCVVS7YAugH4ALH0wHqAh9odHRwOi8vcHJlYmlkLm9yZzo5OTk5L2dwdC5odG1sgAMAiAMBkAMAmAMFoAMBqgMAsAMAuAMAwAOsAsgDANgDAOADAOgDAPgDA4AEAJIEBC9qcHSYBACiBAoxMC4xLjEzLjM3qAQAsgQICAAQABgAIAC4BADABADIBADSBAoxMC4wLjg1Ljkx&s=1bf15e8cdc7c0c8c119614c6386ab1496560da39&referrer=http%3A%2F%2Fprebid.org%3A9999%2Fgpt.html", + "responseTimestamp": 1462919239340, + "requestTimestamp": 1462919238919, + "bidder": "appnexus", + "adUnitCode": "/19968336/header-bid-tag-0", + "timeToRespond": 421, + "pbLg": "5.00", + "pbMg": "10.00", + "pbHg": "10.00", + "pbAg": "10.00", + "size": "300x250", + "alwaysUseBid": true, + "adserverTargeting": { + "hb_bidder": "appnexus", + "hb_adid": "233bcbee889d46d", + "hb_pb": "10.00", + "hb_size": "300x250", + "foobar": "300x250" + } + }, + { + "bidderCode": "appnexus", + "width": 728, + "height": 90, + "statusMessage": "Bid available", + "adId": "24bd938435ec3fc", + "creative_id": 33989846, + "cpm": 10, + "adUrl": "http://lax1-ib.adnxs.com/ab?e=wqT_3QLyBKhyAgAAAwDWAAUBCMjAybkFEOOryfjI7rGNWhjL84KE1tzG-kkgASotCQAAAQII4D8RAQcQAADgPxkJCQjwPyEJCQjgPykRCaAwuvekAji-B0C-B0gCUNbJmhBYweAnYABokUB4mt0CgAEBigEDVVNEkgUG8ECYAdgFoAFaqAEBsAEAuAEBwAEDyAEA0AEA2AEA4AEA8AEAigI6dWYoJ2EnLCA0OTQ0NzIsIDE0NjI5MTkyNDApOwEcLHInLCAzMzk4OTg0NjYeAPBvkgLNASFwU2Y1YUFpNjBJY0VFTmJKbWhBWUFDREI0Q2N3QURnQVFBUkl2Z2RRdXZla0FsZ0FZSk1IYUFCd3lnNTRDb0FCcGh5SUFRcVFBUUdZQVFHZ0FRR29BUU93QVFDNUFRQUFBQUFBQU9BX3dRRQkMSEFEZ1A4a0JJNTJDbGs5VjB6X1oVKCRQQV80QUVBOVFFBSw8bUFLS2dNQ0NENkFDQUxVQwUVBEwwCQh0T0FDQU9nQ0FQZ0NBSUFEQVEuLpoCJSFfZ2lqYXdpMtAA8KZ3ZUFuSUFRb2lvREFnZzgu2ALoB-ACx9MB6gIfaHR0cDovL3ByZWJpZC5vcmc6OTk5OS9ncHQuaHRtbIADAIgDAZADAJgDBaADAaoDALADALgDAMADrALIAwDYAwDgAwDoAwD4AwOABACSBAQvanB0mAQAogQKMTAuMS4xMy4zN6gEi-wJsgQICAAQABgAIAC4BADABADIBADSBAsxMC4wLjgwLjI0MA..&s=1f584d32c2d7ae3ce3662cfac7ca24e710bc7fd0&referrer=http%3A%2F%2Fprebid.org%3A9999%2Fgpt.html", + "responseTimestamp": 1462919239342, + "requestTimestamp": 1462919238919, + "bidder": "appnexus", + "adUnitCode": "/19968336/header-bid-tag1", + "timeToRespond": 423, + "pbLg": "5.00", + "pbMg": "10.00", + "pbHg": "10.00", + "pbAg": "10.00", + "size": "728x90", + "alwaysUseBid": true, + "adserverTargeting": { + "hb_bidder": "appnexus", + "hb_adid": "24bd938435ec3fc", + "hb_pb": "10.00", + "hb_size": "728x90", + "foobar": "728x90" + } + }, + { + "bidderCode": "pagescience", + "width": 300, + "height": 250, + "statusMessage": "Bid available", + "adId": "25bedd4813632d7", + "creative_id": 29681110, + "cpm": 0.5, + "adUrl": "http://lax1-ib.adnxs.com/ab?e=wqT_3QLzBKhzAgAAAwDWAAUBCMjAybkFEM7fioW41qjIQRjL84KE1tzG-kkgASotCQAAAQII4D8RAQcQAADgPxkJCQjwPyEJCQjgPykRCaAwuvekAji-B0C-B0gCUNbLkw5YweAnYABokUB4yIsEgAEBigEDVVNEkgUG8FKYAawCoAH6AagBAbABALgBAcABA8gBANABANgBAOABAPABAIoCOnVmKCdhJywgNDk0NDcyLCAxNDYyOTE5MjQwKTt1ZigncicsIDI5NjgxMTEwLDIeAPBvkgLNASFfeWVLYndpNjBJY0VFTmJMa3c0WUFDREI0Q2N3QURnQVFBUkl2Z2RRdXZla0FsZ0FZSk1IYUFCdzNBMTRDb0FCcGh5SUFRcVFBUUdZQVFHZ0FRR29BUU93QVFDNUFRQUFBQUFBQU9BX3dRRQkMSEFEZ1A4a0JSR3RLaGp1UTFEX1oVKCRQQV80QUVBOVFFBSw8bUFLS2dQVFNES0FDQUxVQwUVBEwwCQhwT0FDQU9nQ0FQZ0NBSUFEQVEuLpoCJSFlQWwtYkE20ADwpndlQW5JQVFvaW9EMDBndy7YAugH4ALH0wHqAh9odHRwOi8vcHJlYmlkLm9yZzo5OTk5L2dwdC5odG1sgAMAiAMBkAMAmAMFoAMBqgMAsAMAuAMAwAOsAsgDANgDAOADAOgDAPgDA4AEAJIEBC9qcHSYBACiBAoxMC4xLjEzLjM3qASL7AmyBAgIABAAGAAgALgEAMAEAMgEANIECzEwLjAuOTMuMjAy&s=1fd8d5650fa1fb8d918a2f403d6a1f97c10d7ec2&referrer=http%3A%2F%2Fprebid.org%3A9999%2Fgpt.html", + "responseTimestamp": 1462919239343, + "requestTimestamp": 1462919238943, + "bidder": "pagescience", + "adUnitCode": "/19968336/header-bid-tag-0", + "timeToRespond": 400, + "pbLg": "0.50", + "pbMg": "0.50", + "pbHg": "0.50", + "pbAg": "0.50", + "size": "300x250", + "adserverTargeting": { + "hb_bidder": "pagescience", + "hb_adid": "25bedd4813632d7", + "hb_pb": "10.00", + "hb_size": "300x250", + "foobar": "300x250" + } + }, + { + "bidderCode": "brightcom", + "width": 300, + "height": 250, + "statusMessage": "Bid available", + "adId": "26e0795ab963896", + "cpm": 0.17, + "ad": "", + "responseTimestamp": 1462919239420, + "requestTimestamp": 1462919238937, + "bidder": "brightcom", + "adUnitCode": "/19968336/header-bid-tag-0", + "timeToRespond": 483, + "pbLg": "0.00", + "pbMg": "0.10", + "pbHg": "0.17", + "pbAg": "0.15", + "size": "300x250", + "adserverTargeting": { + "hb_bidder": "brightcom", + "hb_adid": "26e0795ab963896", + "hb_pb": "10.00", + "hb_size": "300x250", + "foobar": "300x250" + } + }, + { + "bidderCode": "brealtime", + "width": 300, + "height": 250, + "statusMessage": "Bid available", + "adId": "275bd666f5a5a5d", + "creative_id": 29681110, + "cpm": 0.5, + "adUrl": "http://lax1-ib.adnxs.com/ab?e=wqT_3QLzBKhzAgAAAwDWAAUBCMjAybkFEIPr4YfMvKLoQBjL84KE1tzG-kkgASotCQAAAQII4D8RAQcQAADgPxkJCQjwPyEJCQjgPykRCaAwuvekAji-B0C-B0gCUNbLkw5YweAnYABokUB4mo8EgAEBigEDVVNEkgUG8FKYAawCoAH6AagBAbABALgBAcABA8gBANABANgBAOABAPABAIoCOnVmKCdhJywgNDk0NDcyLCAxNDYyOTE5MjQwKTt1ZigncicsIDI5NjgxMTEwLDIeAPBvkgLNASFsU2NQWlFpNjBJY0VFTmJMa3c0WUFDREI0Q2N3QURnQVFBUkl2Z2RRdXZla0FsZ0FZSk1IYUFCdzNBMTRDb0FCcGh5SUFRcVFBUUdZQVFHZ0FRR29BUU93QVFDNUFRQUFBQUFBQU9BX3dRRQkMSEFEZ1A4a0JHZmNvazFBejFUX1oVKCRQQV80QUVBOVFFBSw8bUFLS2dOU0NEYUFDQUxVQwUVBEwwCQh0T0FDQU9nQ0FQZ0NBSUFEQVEuLpoCJSFDUWxfYXdpMtAA8KZ3ZUFuSUFRb2lvRFVnZzAu2ALoB-ACx9MB6gIfaHR0cDovL3ByZWJpZC5vcmc6OTk5OS9ncHQuaHRtbIADAIgDAZADAJgDBaADAaoDALADALgDAMADrALIAwDYAwDgAwDoAwD4AwOABACSBAQvanB0mAQAogQKMTAuMS4xMy4zN6gEi-wJsgQICAAQABgAIAC4BADABADIBADSBAsxMC4wLjg1LjIwOA..&s=975cfe6518f064683541240f0d780d93a5f973da&referrer=http%3A%2F%2Fprebid.org%3A9999%2Fgpt.html", + "responseTimestamp": 1462919239486, + "requestTimestamp": 1462919238941, + "bidder": "brealtime", + "adUnitCode": "/19968336/header-bid-tag-0", + "timeToRespond": 545, + "pbLg": "0.50", + "pbMg": "0.50", + "pbHg": "0.50", + "pbAg": "0.50", + "size": "300x250", + "adserverTargeting": { + "hb_bidder": "brealtime", + "hb_adid": "275bd666f5a5a5d", + "hb_pb": "10.00", + "hb_size": "300x250", + "foobar": "300x250" + } + }, + { + "bidderCode": "pubmatic", + "width": "300", + "height": "250", + "statusMessage": "Bid available", + "adId": "28f4039c636b6a7", + "adSlot": "39620189@300x250", + "cpm": 5.9396, + "ad": "\r
    ", + "dealId": "", + "responseTimestamp": 1462919239544, + "requestTimestamp": 1462919238922, + "bidder": "pubmatic", + "adUnitCode": "/19968336/header-bid-tag-0", + "timeToRespond": 622, + "pbLg": "5.00", + "pbMg": "5.90", + "pbHg": "5.93", + "pbAg": "5.90", + "size": "300x250", + "adserverTargeting": { + "hb_bidder": "pubmatic", + "hb_adid": "28f4039c636b6a7", + "hb_pb": "10.00", + "hb_size": "300x250", + "foobar": "300x250" + } + }, + { + "bidderCode": "rubicon", + "width": 300, + "height": 600, + "statusMessage": "Bid available", + "adId": "29019e2ab586a5a", + "cpm": 2.74, + "ad": "", + "responseTimestamp": 1462919239860, + "requestTimestamp": 1462919238934, + "bidder": "rubicon", + "adUnitCode": "/19968336/header-bid-tag-0", + "timeToRespond": 926, + "pbLg": "2.50", + "pbMg": "2.70", + "pbHg": "2.74", + "pbAg": "2.70", + "size": "300x600", + "adserverTargeting": { + "hb_bidder": "rubicon", + "hb_adid": "29019e2ab586a5a", + "hb_pb": "10.00", + "hb_size": "300x600", + "foobar": "300x600" + } + } + ]; +} + +export function getSlotTargeting() { + return { + "/19968336/header-bid-tag-0": [ + { + "hb_bidder": [ + "appnexus" + ] + }, + { + "hb_adid": [ + "233bcbee889d46d" + ] + }, + { + "hb_pb": [ + "10.00" + ] + }, + { + "hb_size": [ + "300x250" + ] + }, + { + "foobar": [ + "300x250" + ] + } + ] + }; +} + +export function getAdUnits() { + return [ + { + "code": "/19968336/header-bid-tag1", + "sizes": [ + [ + 728, + 90 + ], + [ + 970, + 90 + ] + ], + "bids": [ + { + "bidder": "adequant", + "params": { + "publisher_id": "1234567", + "bidfloor": 0.01 + }, + "placementCode": "/19968336/header-bid-tag1", + "sizes": [ + [ + 728, + 90 + ], + [ + 970, + 90 + ] + ], + "bidId": "3692954f816efc", + "bidderRequestId": "2b1a75d5e826c4", + "requestId": "1ff753bd4ae5cb" + }, + { + "bidder": "appnexus", + "params": { + "placementId": "543221", + "test": "me" + }, + "placementCode": "/19968336/header-bid-tag1", + "sizes": [ + [ + 728, + 90 + ], + [ + 970, + 90 + ] + ], + "bidId": "68136e1c47023d", + "bidderRequestId": "55e24a66bed717", + "requestId": "1ff753bd4ae5cb", + "startTime": 1463510220995, + "status": 1 + } + ] + }, + { + "code": "/19968336/header-bid-tag-0", + "sizes": [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + "bids": [ + { + "bidder": "appnexus", + "params": { + "placementId": "5324321" + }, + "placementCode": "/19968336/header-bid-tag-0", + "sizes": [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + "bidId": "7e5d6af25ed188", + "bidderRequestId": "55e24a66bed717", + "requestId": "1ff753bd4ae5cb", + "startTime": 1463510220996 + }, + { + "bidder": "adequant", + "params": { + "publisher_id": "12353433", + "bidfloor": 0.01 + }, + "placementCode": "/19968336/header-bid-tag-0", + "sizes": [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + "bidId": "4448d80ac1374e", + "bidderRequestId": "2b1a75d5e826c4", + "requestId": "1ff753bd4ae5cb" + }, + { + "bidder": "triplelift", + "params": { + "inventoryCode": "inv_code_here" + }, + "placementCode": "/19968336/header-bid-tag-0", + "sizes": [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + "bidId": "9514d586c52abf", + "bidderRequestId": "8c4f03b838d7ee", + "requestId": "1ff753bd4ae5cb", + "startTime": 1463510220997 + }, + { + "bidder": "springserve", + "params": { + "impId": 1234, + "supplyPartnerId": 1, + "test": true + }, + "placementCode": "/19968336/header-bid-tag-0", + "sizes": [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + "bidId": "113079fed03f58c", + "bidderRequestId": "1048e0df882e965", + "requestId": "1ff753bd4ae5cb" + }, + { + "bidder": "rubicon", + "params": { + "accountId": "123456", + "siteId": "345678", + "zoneId": "234567", + "userId": "12346", + "keywords": [ + "a", + "b", + "c" + ], + "inventory": { + "rating": "5-star", + "prodtype": "tech" + }, + "visitor": { + "ucat": "new", + "search": "iphone" + }, + "sizes": [ + 15, + 10 + ] + }, + "placementCode": "/19968336/header-bid-tag-0", + "sizes": [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + "bidId": "13c2c2a79d155ea", + "bidderRequestId": "129e383ac549e5d", + "requestId": "1ff753bd4ae5cb" + }, + { + "bidder": "openx", + "params": { + "jstag_url": "http://servedbyopenx.com/w/1.0/jstag?nc=account_key", + "unit": 2345677 + }, + "placementCode": "/19968336/header-bid-tag-0", + "sizes": [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + "bidId": "154f9cbf82df565", + "bidderRequestId": "1448569c2453b84", + "requestId": "1ff753bd4ae5cb" + }, + { + "bidder": "pubmatic", + "params": { + "publisherId": 1234567, + "adSlot": "1234567@300x250" + }, + "placementCode": "/19968336/header-bid-tag-0", + "sizes": [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + "bidId": "17f8c3a8fb13308", + "bidderRequestId": "16095445eeb05e4", + "requestId": "1ff753bd4ae5cb" + }, + { + "bidder": "pagescience", + "params": { + "placementId": "1234567" + }, + "placementCode": "/19968336/header-bid-tag-0", + "sizes": [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + "bidId": "2074d5757675542", + "bidderRequestId": "19883380ef5453a", + "requestId": "1ff753bd4ae5cb", + "startTime": 1463510221014 + }, + { + "bidder": "brealtime", + "params": { + "placementId": "1234567" + }, + "placementCode": "/19968336/header-bid-tag-0", + "sizes": [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + "bidId": "222b6ad5a9b835d", + "bidderRequestId": "2163409fdf6f333", + "requestId": "1ff753bd4ae5cb", + "startTime": 1463510221015 + }, + { + "bidder": "indexExchange", + "params": { + "id": "1", + "siteID": 123456, + "timeout": 10000 + }, + "placementCode": "/19968336/header-bid-tag-0", + "sizes": [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + "bidId": "2499961ab3f937a", + "bidderRequestId": "23b57a2de4ae50b", + "requestId": "1ff753bd4ae5cb" + }, + { + "bidder": "adform", + "params": { + "adxDomain": "adx.adform.net", + "mid": 123456, + "test": 1 + }, + "placementCode": "/19968336/header-bid-tag-0", + "sizes": [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + "bidId": "26605265bf5e9c5", + "bidderRequestId": "25a0902299c17d3", + "requestId": "1ff753bd4ae5cb" + }, + { + "bidder": "amazon", + "params": { + "aId": 3080 + }, + "placementCode": "/19968336/header-bid-tag-0", + "sizes": [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + "bidId": "2935d8f6764fe45", + "bidderRequestId": "28afa21ca9246c1", + "requestId": "1ff753bd4ae5cb" + }, + { + "bidder": "aol", + "params": { + "network": "112345.45", + "placement": 12345 + }, + "placementCode": "/19968336/header-bid-tag-0", + "sizes": [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + "bidId": "31d1489681dc539", + "bidderRequestId": "30bf32da9080fdd", + "requestId": "1ff753bd4ae5cb" + }, + { + "bidder": "sovrn", + "params": { + "tagid": "123556" + }, + "placementCode": "/19968336/header-bid-tag-0", + "sizes": [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + "bidId": "33c1a8028d91563", + "bidderRequestId": "324bcb47cfcf034", + "requestId": "1ff753bd4ae5cb" + }, + { + "bidder": "pulsepoint", + "params": { + "cf": "300X250", + "cp": 1233456, + "ct": 12357 + }, + "placementCode": "/19968336/header-bid-tag-0", + "sizes": [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + "bidId": "379219f0506a26f", + "bidderRequestId": "360ec66bbb0719c", + "requestId": "1ff753bd4ae5cb" + }, + { + "bidder": "brightcom", + "params": { + "tagId": 75423 + }, + "placementCode": "/19968336/header-bid-tag-0", + "sizes": [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + "bidId": "395cfcf496e7d6d", + "bidderRequestId": "38a776c7f001ea", + "requestId": "1ff753bd4ae5cb" + } + ] + }, + { + "code": "/7780971/apex_sparks_300", + "sizes": [ + [ + 300, + 250 + ] + ], + "bids": [ + { + "bidder": "sonobi", + "params": { + "dom_id": "div-gpt-ad-1455548812677-0", + "ad_unit": "/7780971/apex_sparks_300" + }, + "placementCode": "/7780971/apex_sparks_300", + "sizes": [ + [ + 300, + 250 + ] + ], + "bidId": "43a45a2cd8c6ef9", + "bidderRequestId": "42e2fe519b7c653", + "requestId": "1ff753bd4ae5cb" + } + ] + }, + { + "code": "/7780971/apex_sparks_skyscraper_x600", + "sizes": [ + [ + 300, + 600 + ] + ], + "bids": [ + { + "bidder": "sonobi", + "params": { + "dom_id": "div-gpt-ad-1455548812677-1", + "placement_id": "21d2da738fe0ba795cfb-test" + }, + "placementCode": "/7780971/apex_sparks_skyscraper_x600", + "sizes": [ + [ + 300, + 600 + ] + ], + "bidId": "44f16af92ca7607", + "bidderRequestId": "42e2fe519b7c653", + "requestId": "1ff753bd4ae5cb" + } + ] + }, + { + "code": "/7780971/apex_sparks_banner_x90", + "sizes": [ + [ + 728, + 90 + ] + ], + "bids": [ + { + "bidder": "sonobi", + "params": { + "dom_id": "div-gpt-ad-1455548812677-2", + "ad_unit": "/7780971/apex_sparks_banner_x90" + }, + "placementCode": "/7780971/apex_sparks_banner_x90", + "sizes": [ + [ + 728, + 90 + ] + ], + "bidId": "45e5c0084508efb", + "bidderRequestId": "42e2fe519b7c653", + "requestId": "1ff753bd4ae5cb" + } + ] + } + ] +}; + +export function getAdServerTargeting() { + return { + "/19968336/header-bid-tag-0": { + "foobar": "300x250", + "hb_size": "300x250", + "hb_pb": "10.00", + "hb_adid": "233bcbee889d46d", + "hb_bidder": "appnexus", + "hb_size_triplelift": "0x0", + "hb_pb_triplelift": "10.00", + "hb_adid_triplelift": "222bb26f9e8bd", + "hb_bidder_triplelift": "triplelift", + "hb_size_appnexus": "300x250", + "hb_pb_appnexus": "10.00", + "hb_adid_appnexus": "233bcbee889d46d", + "hb_bidder_appnexus": "appnexus", + "hb_size_pagescience": "300x250", + "hb_pb_pagescience": "10.00", + "hb_adid_pagescience": "25bedd4813632d7", + "hb_bidder_pagescienc": "pagescience", + "hb_size_brightcom": "300x250", + "hb_pb_brightcom": "10.00", + "hb_adid_brightcom": "26e0795ab963896", + "hb_bidder_brightcom": "brightcom", + "hb_size_brealtime": "300x250", + "hb_pb_brealtime": "10.00", + "hb_adid_brealtime": "275bd666f5a5a5d", + "hb_bidder_brealtime": "brealtime", + "hb_size_pubmatic": "300x250", + "hb_pb_pubmatic": "10.00", + "hb_adid_pubmatic": "28f4039c636b6a7", + "hb_bidder_pubmatic": "pubmatic", + "hb_size_rubicon": "300x600", + "hb_pb_rubicon": "10.00", + "hb_adid_rubicon": "29019e2ab586a5a", + "hb_bidder_rubicon": "rubicon" + }, + "/19968336/header-bid-tag1": { + "foobar": "728x90", + "hb_size": "728x90", + "hb_pb": "10.00", + "hb_adid": "24bd938435ec3fc", + "hb_bidder": "appnexus", + "hb_size_appnexus": "728x90", + "hb_pb_appnexus": "10.00", + "hb_adid_appnexus": "24bd938435ec3fc", + "hb_bidder_appnexus": "appnexus" + } + }; +} diff --git a/test/fixtures/targeting-map.json b/test/fixtures/targeting-map.json index 373e538a08e..1984c7cf4d5 100644 --- a/test/fixtures/targeting-map.json +++ b/test/fixtures/targeting-map.json @@ -1,8 +1,29 @@ { - "/123456/header-bid-tag-0": { - "hb_bidder": "rubicon", - "hb_adid": "148018fe5e", - "hb_pb": "10.00", - "foobar": "300x250" - } + "/19968336/header-bid-tag-0": [ + { + "hb_bidder": [ + "appnexus" + ] + }, + { + "hb_adid": [ + "233bcbee889d46d" + ] + }, + { + "hb_pb": [ + "10.00" + ] + }, + { + "hb_size": [ + "300x250" + ] + }, + { + "foobar": [ + "300x250" + ] + } + ] } \ No newline at end of file diff --git a/test/spec/bidmanager_spec.js b/test/spec/bidmanager_spec.js index 1c4dc45f39a..b5166ab6917 100644 --- a/test/spec/bidmanager_spec.js +++ b/test/spec/bidmanager_spec.js @@ -5,6 +5,8 @@ var assert = require("assert"); //TODO refactor to use the spec files var utils = require('../../src/utils'); var bidmanager = require('../../src/bidmanager'); +var bidfactory = require('../../src/bidfactory'); +var fixtures = require('../fixtures/fixtures'); describe('replaceTokenInString', function () { @@ -347,4 +349,46 @@ describe('bidmanager.js', function () { }); }); + + describe('addBidResponse', () => { + before(() => { + pbjs.adUnits = fixtures.getAdUnits(); + }); + + it('should return proper price bucket increments for dense mode', () => { + const bid = Object.assign({}, + bidfactory.createBid(2), + fixtures.getBidResponses()[5] + ); + + // 0 - 3 dollars + bid.cpm = '1.99'; + let expectedIncrement = '1.99'; + bidmanager.addBidResponse(bid.adUnitCode, bid); + // pop this bid because another test relies on global pbjs._bidsReceived + let registeredBid = pbjs._bidsReceived.pop(); + assert.equal(registeredBid.pbDg, expectedIncrement, '0 - 3 hits at to 1 cent increment'); + + // 3 - 8 dollars + bid.cpm = '4.39'; + expectedIncrement = '4.35'; + bidmanager.addBidResponse(bid.adUnitCode, bid); + registeredBid = pbjs._bidsReceived.pop(); + assert.equal(registeredBid.pbDg, expectedIncrement, '3 - 8 hits at 5 cent increment'); + + // 8 - 20 dollars + bid.cpm = '19.99'; + expectedIncrement = '19.50'; + bidmanager.addBidResponse(bid.adUnitCode, bid); + registeredBid = pbjs._bidsReceived.pop(); + assert.equal(registeredBid.pbDg, expectedIncrement, '8 - 20 hits at 50 cent increment'); + + // 20+ dollars + bid.cpm = '73.07'; + expectedIncrement = '20.00'; + bidmanager.addBidResponse(bid.adUnitCode, bid); + registeredBid = pbjs._bidsReceived.pop(); + assert.equal(registeredBid.pbDg, expectedIncrement, '20+ caps at 20.00'); + }); + }); }); diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index 2365c7e13f9..c0a84b52f3d 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -1,3 +1,5 @@ +import { getBidRequests, getBidResponses, getAdServerTargeting } from 'test/fixtures/fixtures'; + var assert = require('chai').assert; var prebid = require('src/prebid'); @@ -7,8 +9,10 @@ var bidmanager = require('src/bidmanager'); var bidResponses = require('test/fixtures/bid-responses.json'); var targetingMap = require('test/fixtures/targeting-map.json'); var config = require('test/fixtures/config.json'); -var targetingString = 'hb_bidder=rubicon&hb_adid=148018fe5e&hb_pb=10.00&foobar=300x250&'; -var spyLogMessage = sinon.spy(utils, 'logMessage'); + +pbjs = pbjs || {}; +pbjs._bidsRequested = getBidRequests(); +pbjs._bidsReceived = getBidResponses(); var Slot = function Slot(elementId, pathId) { var slot = { @@ -21,7 +25,15 @@ var Slot = function Slot(elementId, pathId) { }, setTargeting: function setTargeting(key, value) { - } + }, + + getTargeting: function getTargeting() { + return [{ testKey: ['a test targeting value'] }]; + }, + + getTargetingKeys: function getTargetingKeys() { + return ['testKey']; + } }; slot.spySetTargeting = sinon.spy(slot, 'setTargeting'); return slot; @@ -29,9 +41,9 @@ var Slot = function Slot(elementId, pathId) { var createSlotArray = function createSlotArray() { return [ - new Slot(config.adUnitElementIDs[0], config.adUnitCodes[0]), - new Slot(config.adUnitElementIDs[1], config.adUnitCodes[1]), - new Slot(config.adUnitElementIDs[2], config.adUnitCodes[2]) + new Slot(config.adUnitElementIDs[0], config.adUnitCodes[0]), + new Slot(config.adUnitElementIDs[1], config.adUnitCodes[1]), + new Slot(config.adUnitElementIDs[2], config.adUnitCodes[2]) ]; }; @@ -51,97 +63,81 @@ window.googletag = { } }; -bidmanager.pbBidResponseByPlacement = bidResponses; - -after(function () { - utils.logMessage.restore(); -}); - describe('Unit: Prebid Module', function () { + describe('getAdserverTargetingForAdUnitCodeStr', function () { it('should return targeting info as a string', function () { - var result = pbjs.getAdserverTargetingForAdUnitCodeStr(config.adUnitCodes[0]); - assert.equal(result, targetingString, 'returns expected string of ad targeting info'); + const adUnitCode = config.adUnitCodes[0]; + pbjs.enableSendAllBids(); + var expected = 'foobar=300x250&hb_size=300x250&hb_pb=10.00&hb_adid=233bcbee889d46d&hb_bidder=appnexus&hb_size_triplelift=0x0&hb_pb_triplelift=10.00&hb_adid_triplelift=222bb26f9e8bd&hb_bidder_triplelift=triplelift&hb_size_appnexus=300x250&hb_pb_appnexus=10.00&hb_adid_appnexus=233bcbee889d46d&hb_bidder_appnexus=appnexus&hb_size_pagescience=300x250&hb_pb_pagescience=10.00&hb_adid_pagescience=25bedd4813632d7&hb_bidder_pagescienc=pagescience&hb_size_brightcom=300x250&hb_pb_brightcom=10.00&hb_adid_brightcom=26e0795ab963896&hb_bidder_brightcom=brightcom&hb_size_brealtime=300x250&hb_pb_brealtime=10.00&hb_adid_brealtime=275bd666f5a5a5d&hb_bidder_brealtime=brealtime&hb_size_pubmatic=300x250&hb_pb_pubmatic=10.00&hb_adid_pubmatic=28f4039c636b6a7&hb_bidder_pubmatic=pubmatic&hb_size_rubicon=300x600&hb_pb_rubicon=10.00&hb_adid_rubicon=29019e2ab586a5a&hb_bidder_rubicon=rubicon'; + var result = pbjs.getAdserverTargetingForAdUnitCodeStr(adUnitCode); + assert.equal(expected, result, 'returns expected string of ad targeting info'); }); it('should log message if adunitCode param is falsey', function () { + var spyLogMessage = sinon.spy(utils, 'logMessage'); var result = pbjs.getAdserverTargetingForAdUnitCodeStr(); assert.ok(spyLogMessage.calledWith('Need to call getAdserverTargetingForAdUnitCodeStr with adunitCode'), 'expected message was logged'); assert.equal(result, undefined, 'result is undefined'); + utils.logMessage.restore(); }); }); describe('getAdserverTargetingForAdUnitCode', function () { it('should return targeting info as an object', function () { - var result = pbjs.getAdserverTargetingForAdUnitCode(config.adUnitCodes[0]); - assert.deepEqual(result, targetingMap[config.adUnitCodes[0]], 'returns expected targeting info object'); - }); - - it('should return full targeting map object if adunitCode is falsey', function () { - var result = pbjs.getAdserverTargetingForAdUnitCode(); - assert.deepEqual(result, targetingMap, 'the complete targeting map object is returned'); + const adUnitCode = config.adUnitCodes[0]; + pbjs.enableSendAllBids(); + var result = pbjs.getAdserverTargetingForAdUnitCode(adUnitCode); + const expected = getAdServerTargeting()[adUnitCode]; + assert.deepEqual(result, expected, 'returns expected' + + ' targeting info object'); }); }); describe('getAdServerTargeting', function () { - it('should call getAdServerTargetingForAdUnitCode', function () { - var spyGetAdServerTargetingForAdUnitCode = sinon.spy(pbjs, 'getAdserverTargetingForAdUnitCode'); - pbjs.getAdserverTargeting(); - assert.ok(spyGetAdServerTargetingForAdUnitCode.calledOnce, 'called the expected function'); - pbjs.getAdserverTargetingForAdUnitCode.restore(); + it('should return current targeting data for slots', function () { + const targeting = pbjs.getAdserverTargeting(); + const expected = getAdServerTargeting(); + pbjs.enableSendAllBids(); + assert.deepEqual(targeting, expected, 'targeting ok'); }); }); describe('getBidResponses', function () { - it('should return expected bid responses when passed an adunitCode', function () { - var result = pbjs.getBidResponses(config.adUnitCodes[0]); - var compare = require('test/fixtures/bid-responses-cloned.json')[config.adUnitCodes[0]]; - - assert.deepEqual(result, compare); - }); - it('should return expected bid responses when not passed an adunitCode', function () { var result = pbjs.getBidResponses(); - var compare = require('test/fixtures/bid-responses-cloned.json'); - - assert.deepEqual(result, compare); + var compare = getBidResponses().map(bid => bid.adUnitCode) + .filter((v, i, a) => a.indexOf(v) === i).map(adUnitCode => pbjs._bidsReceived + .filter(bid => bid.adUnitCode === adUnitCode)) + .map(bids => { + return { + [bids[0].adUnitCode]: { bids: bids } + }; + }) + .reduce((a, b) => Object.assign(a, b), {}); + + assert.deepEqual(result, compare, 'expected bid responses are returned'); }); }); describe('getBidResponsesForAdUnitCode', function () { - it('should call getBidResponses with passed in adUnitCode', function () { - var adUnitCode = 'xyz'; - var spyGetBidResponses = sinon.spy(pbjs, 'getBidResponses'); - - pbjs.getBidResponsesForAdUnitCode(adUnitCode); - assert.ok(spyGetBidResponses.calledWith(adUnitCode)); - pbjs.getBidResponses.restore(); + it('should return bid responses as expected', function () { + const adUnitCode = '/19968336/header-bid-tag-0'; + const result = pbjs.getBidResponsesForAdUnitCode(adUnitCode); + const bids = getBidResponses().filter(bid => bid.adUnitCode === adUnitCode); + const compare = { bids: bids}; + assert.deepEqual(result, compare, 'expected id responses for ad unit code are returned'); }); }); describe('setTargetingForGPTAsync', function () { - it('should log a message when googletag functions not defined', function () { - var pubads = window.googletag.pubads; - - window.googletag.pubads = undefined; - pbjs.setTargetingForAdUnitsGPTAsync(); - spyLogMessage.calledWith('window.googletag is not defined on the page'); - window.googletag.pubads = pubads; - }); it('should set targeting when passed an array of ad unit codes', function () { var slots = createSlotArray(); window.googletag.pubads().setSlots(slots); pbjs.setTargetingForGPTAsync(config.adUnitCodes); - assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_bidder', ''), 'clears hb_bidder param'); - assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_adid', ''), 'clears hb_adid param'); - assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_pb', ''), 'clears hb_pb param'); - assert.ok(slots[0].spySetTargeting.calledWithExactly('foobar', ''), 'clears foobar param'); - assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_bidder', 'rubicon'), 'sets hb_bidder param'); - assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_adid', '148018fe5e'), 'sets hb_adid param'); - assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_pb', '10.00'), 'sets hb_pb param'); - assert.ok(slots[0].spySetTargeting.calledWithExactly('foobar', '300x250'), 'sets foobar param'); + assert.deepEqual(slots[0].spySetTargeting.args[0], ['hb_bidder', 'appnexus'], 'slot.setTargeting was called with expected key/values'); }); it('should set targeting from googletag data', function () { @@ -149,48 +145,25 @@ describe('Unit: Prebid Module', function () { window.googletag.pubads().setSlots(slots); pbjs.setTargetingForGPTAsync(); - assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_bidder', ''), 'clears hb_bidder param'); - assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_adid', ''), 'clears hb_adid param'); - assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_pb', ''), 'clears hb_pb param'); - assert.ok(slots[0].spySetTargeting.calledWithExactly('foobar', ''), 'clears foobar param'); - assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_bidder', 'rubicon'), 'sets hb_bidder param'); - assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_adid', '148018fe5e'), 'sets hb_adid param'); - assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_pb', '10.00'), 'sets hb_pb param'); - assert.ok(slots[0].spySetTargeting.calledWithExactly('foobar', '300x250'), 'sets foobar param'); }); it('Calling enableSendAllBids should set targeting to include standard keys with bidder' + - ' append to key name', function() { + ' append to key name', function () { var slots = createSlotArray(); window.googletag.pubads().setSlots(slots); pbjs.enableSendAllBids(); pbjs.setTargetingForGPTAsync(); - assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_bidder', ''), 'clears hb_bidder param'); - assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_adid', ''), 'clears hb_adid param'); - assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_pb', ''), 'clears hb_pb param'); - assert.ok(slots[0].spySetTargeting.calledWithExactly('foobar', ''), 'clears foobar param'); - assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_bidder', 'rubicon'), 'sets hb_bidder param'); - assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_adid', '148018fe5e'), 'sets hb_adid param'); - assert.ok(slots[0].spySetTargeting.calledWithExactly('hb_pb', '10.00'), 'sets hb_pb param'); - assert.ok(slots[0].spySetTargeting.calledWithExactly('foobar', '300x250'), 'sets foobar param'); - assert.ok(slots[0].spySetTargeting.calledWith('hb_bidder_rubicon', ''), 'sets' + - ' hb_bidder_rubicon param'); - assert.ok(slots[0].spySetTargeting.calledWith('hb_adid_rubicon', ''), 'sets hb_adid_rubicon param'); - assert.ok(slots[0].spySetTargeting.calledWith('hb_pb_rubicon', ''), 'sets hb_pb_rubicon' + - ' param'); - assert.ok(!slots[0].spySetTargeting.calledWithExactly('foobar_rubicon', ''), 'does' + - ' not set custom keys with bidder in key name for foobar_rubicon param'); }); }); describe('allBidsAvailable', function () { it('should call bidmanager.allBidsBack', function () { - var spyAllBidsBack = sinon.spy(bidmanager, 'allBidsBack'); + var spyAllBidsBack = sinon.spy(bidmanager, 'bidsBackAll'); pbjs.allBidsAvailable(); assert.ok(spyAllBidsBack.called, 'called bidmanager.allBidsBack'); - bidmanager.allBidsBack.restore(); + bidmanager.bidsBackAll.restore(); }); }); @@ -199,8 +172,9 @@ describe('Unit: Prebid Module', function () { var doc = {}; var adResponse = {}; var spyLogError = null; + var spyLogMessage = null; - beforeEach(function() { + beforeEach(function () { doc = { write: sinon.spy(), close: sinon.spy(), @@ -213,17 +187,20 @@ describe('Unit: Prebid Module', function () { }; adResponse = { + "adId": bidId, "width": 300, "height": 250, }; - bidmanager._adResponsesByBidderId[bidId] = adResponse; + pbjs._bidsReceived.push(adResponse); spyLogError = sinon.spy(utils, 'logError'); + spyLogMessage = sinon.spy(utils, 'logMessage'); }); - afterEach(function() { - bidmanager._adResponsesByBidderId[bidId] = null; + afterEach(function () { + pbjs._bidsReceived.splice(pbjs._bidsReceived.indexOf(adResponse), 1); utils.logError.restore(); + utils.logMessage.restore(); }); it('should require doc and id params', function () { @@ -238,30 +215,30 @@ describe('Unit: Prebid Module', function () { assert.ok(spyLogMessage.calledWith(message), 'expected message was logged'); }); - it('should write the ad to the doc', function() { + it('should write the ad to the doc', function () { adResponse.ad = ""; pbjs.renderAd(doc, bidId); assert.ok(doc.write.calledWith(adResponse.ad), 'ad was written to doc'); assert.ok(doc.close.called, 'close method called'); }); - it('should place the url inside an iframe on the doc', function() { + it('should place the url inside an iframe on the doc', function () { adResponse.adUrl = "http://server.example.com/ad/ad.js"; pbjs.renderAd(doc, bidId); var iframe = '' assert.ok(doc.write.calledWith(iframe), 'url was written to iframe in doc'); }); - it('should log an error when no ad or url', function() { + it('should log an error when no ad or url', function () { pbjs.renderAd(doc, bidId); var error = 'Error trying to write ad. No ad for bid response id: ' + bidId; assert.ok(spyLogError.calledWith(error), 'expected error was logged'); }); - it('should catch errors thrown when trying to write ads to the page', function() { + it('should catch errors thrown when trying to write ads to the page', function () { adResponse.ad = ""; - var error = {message: 'doc write error'}; + var error = { message: 'doc write error' }; doc.write = sinon.stub().throws(error); pbjs.renderAd(doc, bidId); @@ -269,7 +246,7 @@ describe('Unit: Prebid Module', function () { assert.ok(spyLogError.calledWith(errorMessage), 'expected error was logged'); }); - it('should log an error when ad not found', function() { + it('should log an error when ad not found', function () { var fakeId = 99; pbjs.renderAd(doc, fakeId); var error = 'Error trying to write ad. Cannot find ad by given id : ' + fakeId; diff --git a/test/spec/utils_spec.js b/test/spec/utils_spec.js index 6b273473a65..bf9fb5e3262 100755 --- a/test/spec/utils_spec.js +++ b/test/spec/utils_spec.js @@ -1,3 +1,5 @@ +import { getSlotTargeting, getAdServerTargeting } from 'test/fixtures/fixtures'; + var assert = require('assert'); var utils = require('../../src/utils'); @@ -97,14 +99,11 @@ describe('Utils', function () { describe('transformAdServerTargetingObj', function () { it('should append query string to existing using the input obj', function () { - var obj = { - a:'1', - b:'2' - }; + var obj = getAdServerTargeting(); - var output = utils.transformAdServerTargetingObj(obj); - var expectedResult = 'a=' + encodeURIComponent('1') + '&b=' + encodeURIComponent('2') + '&'; - assert.equal(output, expectedResult); + var output = utils.transformAdServerTargetingObj(obj[Object.keys(obj)[0]]); + var expected = 'foobar=300x250&hb_size=300x250&hb_pb=10.00&hb_adid=233bcbee889d46d&hb_bidder=appnexus&hb_size_triplelift=0x0&hb_pb_triplelift=10.00&hb_adid_triplelift=222bb26f9e8bd&hb_bidder_triplelift=triplelift&hb_size_appnexus=300x250&hb_pb_appnexus=10.00&hb_adid_appnexus=233bcbee889d46d&hb_bidder_appnexus=appnexus&hb_size_pagescience=300x250&hb_pb_pagescience=10.00&hb_adid_pagescience=25bedd4813632d7&hb_bidder_pagescienc=pagescience&hb_size_brightcom=300x250&hb_pb_brightcom=10.00&hb_adid_brightcom=26e0795ab963896&hb_bidder_brightcom=brightcom&hb_size_brealtime=300x250&hb_pb_brealtime=10.00&hb_adid_brealtime=275bd666f5a5a5d&hb_bidder_brealtime=brealtime&hb_size_pubmatic=300x250&hb_pb_pubmatic=10.00&hb_adid_pubmatic=28f4039c636b6a7&hb_bidder_pubmatic=pubmatic&hb_size_rubicon=300x600&hb_pb_rubicon=10.00&hb_adid_rubicon=29019e2ab586a5a&hb_bidder_rubicon=rubicon'; + assert.equal(output, expected); }); it('should return an empty string, if input obj is empty', function () { From e254321846cf3ce3541f0b9063c55a18154065de Mon Sep 17 00:00:00 2001 From: eyedar Date: Wed, 18 May 2016 07:16:13 -0700 Subject: [PATCH 114/160] added info-level log message to setPriceGranularity() (#354) --- src/prebid.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/prebid.js b/src/prebid.js index 72c10d89539..e64cd8d919f 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -587,6 +587,7 @@ pbjs.aliasBidder = function (bidderCode, alias) { }; pbjs.setPriceGranularity = function (granularity) { + utils.logInfo('Invoking pbjs.setPriceGranularity', arguments); if (!granularity) { utils.logError('Prebid Error: no value passed to `setPriceGranularity()`'); } else { From 5c62d50ab4b57692aed283691c36aaa41aacd8ba Mon Sep 17 00:00:00 2001 From: bretg Date: Wed, 18 May 2016 10:38:14 -0400 Subject: [PATCH 115/160] adding sizes, setting integration type (#345) --- src/adapters/rubicon.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/adapters/rubicon.js b/src/adapters/rubicon.js index 51368bf066c..17968e0c597 100644 --- a/src/adapters/rubicon.js +++ b/src/adapters/rubicon.js @@ -17,13 +17,23 @@ var RubiconAdapter = function RubiconAdapter() { var RUBICON_OK_STATUS = 'ok'; var RUBICON_BIDDER_CODE = 'rubicon'; var RUBICON_SIZE_MAP = { + '468x60': 1, '728x90': 2, + '120x600': 8, '160x600': 9, '300x600': 10, '300x250': 15, + '336x280': 16, '320x50': 43, + '300x50': 44, '300x1050': 54, - '970x250': 57 + '970x90': 55, + '970x250': 57, + '1000x90': 58, + '320x480': 67, + '1800x1000': 68, + '480x320':101, + '768x1024': 102 }; var RUBICON_INITIALIZED = 0; @@ -287,6 +297,7 @@ var RubiconAdapter = function RubiconAdapter() { _bidsReady(slots); }; + window.rubicontag.setIntegration('pbjs'); window.rubicontag.run(callback, parameters); }); } From e0a44983ba1453c143f20404d66c6abcb0ddf433 Mon Sep 17 00:00:00 2001 From: mkendall07 Date: Wed, 18 May 2016 10:46:10 -0400 Subject: [PATCH 116/160] Remove console.log message --- src/adaptermanager.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/adaptermanager.js b/src/adaptermanager.js index 4f4c7a32b54..1b9e4d12758 100644 --- a/src/adaptermanager.js +++ b/src/adaptermanager.js @@ -37,7 +37,6 @@ exports.callBids = () => { bids: getBids({ bidderCode, requestId, bidderRequestId }), start: new Date().getTime() }; - console.log('bid set:', bidderCode, bidderRequestId); utils.logMessage(`CALLING BIDDER ======= ${bidderCode}`); pbjs._bidsRequested.push(bidderRequest); events.emit(CONSTANTS.EVENTS.BID_REQUESTED, bidderRequest); From 5ce94697ff38500c6dba8d9c529543a7e960dbe9 Mon Sep 17 00:00:00 2001 From: Matt Kendall Date: Wed, 18 May 2016 15:07:40 -0400 Subject: [PATCH 117/160] Remove DFP dependency in getWinningBidTargeting * Fix #358 * Updates from code review --- src/prebid.js | 41 +++++++++++++++++++++++++++-------------- src/utils.js | 6 ++++++ 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/src/prebid.js b/src/prebid.js index e64cd8d919f..090ca837f28 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -1,6 +1,6 @@ /** @module pbjs */ -import { flatten, uniques, getKeys } from './utils'; +import { flatten, uniques, getKeys, isGptPubadsDefined } from './utils'; // if pbjs already exists in global document scope, use it, if not, create the object window.pbjs = (window.pbjs || {}); @@ -101,17 +101,20 @@ function checkDefinedPlacement(id) { } function getWinningBidTargeting() { - const presets = (function getPresetTargeting() { - return window.googletag.pubads().getSlots().map(slot => { - return { - [slot.getAdUnitPath()]: slot.getTargetingKeys().map(key => { - return { [key]: slot.getTargeting(key) }; - }) - }; - }); - })(); + let presets; + if (isGptPubadsDefined()) { + presets = (function getPresetTargeting() { + return window.googletag.pubads().getSlots().map(slot => { + return { + [slot.getAdUnitPath()]: slot.getTargetingKeys().map(key => { + return { [key]: slot.getTargeting(key) }; + }) + }; + }); + })(); + } - const winners = pbjs._bidsReceived.map(bid => bid.adUnitCode) + let winners = pbjs._bidsReceived.map(bid => bid.adUnitCode) .filter(uniques) .map(adUnitCode => pbjs._bidsReceived .filter(bid => bid.adUnitCode === adUnitCode ? bid : null) @@ -122,14 +125,20 @@ function getWinningBidTargeting() { adserverTargeting: {} })); - return winners.map(winner => { + winners = winners.map(winner => { return { [winner.adUnitCode]: Object.keys(winner.adserverTargeting, key => key) .map(key => { return { [key.substring(0, 20)]: [winner.adserverTargeting[key]] }; }) }; - }).concat(presets); + }); + + if(presets) { + winners.concat(presets); + } + + return winners; function getHighestCpm(previous, current) { return previous.cpm < current.cpm ? current : previous; @@ -273,6 +282,11 @@ pbjs.getBidResponsesForAdUnitCode = function (adUnitCode) { * @alias module:pbjs.setTargetingForGPTAsync */ pbjs.setTargetingForGPTAsync = function () { + utils.logInfo('Invoking pbjs.setTargetingForGPTAsync', arguments); + if (!isGptPubadsDefined()) { + utils.logError('window.googletag is not defined on the page'); + return; + } window.googletag.pubads().getSlots().forEach(slot => { getAllTargeting() .filter(targeting => Object.keys(targeting)[0] === slot.getAdUnitPath()) @@ -281,7 +295,6 @@ pbjs.setTargetingForGPTAsync = function () { })); }); - utils.logInfo('Invoking pbjs.setTargetingForGPTAsync', arguments); }; /** diff --git a/src/utils.js b/src/utils.js index 629e0202c3d..3b8cba0c6c8 100644 --- a/src/utils.js +++ b/src/utils.js @@ -481,3 +481,9 @@ export function getBidderCodes() { return pbjs.adUnits.map(unit => unit.bids.map(bid => bid.bidder) .reduce(flatten, [])).reduce(flatten).filter(uniques); } + +export function isGptPubadsDefined() { + if (window.googletag && exports.isFn(window.googletag.pubads) && exports.isFn(window.googletag.pubads().getSlots)) { + return true; + } +} From 5c1541982e2bf32661496296764123120d376650 Mon Sep 17 00:00:00 2001 From: Matt Lane Date: Wed, 18 May 2016 15:10:50 -0700 Subject: [PATCH 118/160] Prebid.js 0.9.0 release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5965b0aa6d5..0c04529e4b9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "0.8.1", + "version": "0.9.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 7860a2848adbb93de1bc6bc68bf22e17b329ce76 Mon Sep 17 00:00:00 2001 From: Matt Kendall Date: Thu, 19 May 2016 14:53:31 -0400 Subject: [PATCH 119/160] Test pbjs public api (#361) - Test pbjs.setTargetingForGPTAsync when window.googletag is not defined - Fix adUnit checking logic - Test pbjs.requestBids adUnits branch --- src/prebid.js | 3 +- test/spec/unit/pbjs_api_spec.js | 303 ++++++++++++++++++++++++++++++++ 2 files changed, 304 insertions(+), 2 deletions(-) diff --git a/src/prebid.js b/src/prebid.js index 090ca837f28..dd13cfd4ea5 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -395,8 +395,7 @@ pbjs.requestBids = function ({ bidsBackHandler, timeout }) { utils.logInfo('Invoking pbjs.requestBids', arguments); - // not sure of this logic - if (!pbjs.adUnits && pbjs.adUnits.length !== 0) { + if (!pbjs.adUnits || pbjs.adUnits.length === 0) { utils.logMessage('No adUnits configured. No bids requested.'); return; } diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index c0a84b52f3d..50e2949a426 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -5,6 +5,11 @@ var assert = require('chai').assert; var prebid = require('src/prebid'); var utils = require('src/utils'); var bidmanager = require('src/bidmanager'); +var adloader = require('src/adloader'); +var adaptermanager = require('src/adaptermanager'); +var events = require('src/events'); +var ga = require('src/ga'); +var CONSTANTS = require('src/constants.json'); var bidResponses = require('test/fixtures/bid-responses.json'); var targetingMap = require('test/fixtures/targeting-map.json'); @@ -131,6 +136,9 @@ describe('Unit: Prebid Module', function () { }); describe('setTargetingForGPTAsync', function () { + let logErrorSpy; + beforeEach(() => logErrorSpy = sinon.spy(utils, 'logError')); + afterEach(() => utils.logError.restore()); it('should set targeting when passed an array of ad unit codes', function () { var slots = createSlotArray(); @@ -155,6 +163,16 @@ describe('Unit: Prebid Module', function () { pbjs.enableSendAllBids(); pbjs.setTargetingForGPTAsync(); }); + + it('should log error when googletag is not defined on page', function () { + const error = 'window.googletag is not defined on the page'; + const windowGoogletagBackup = window.googletag; + window.googletag = {}; + + pbjs.setTargetingForGPTAsync(); + assert.ok(logErrorSpy.calledWith(error), 'expected error was logged'); + window.googletag = windowGoogletagBackup; + }); }); describe('allBidsAvailable', function () { @@ -253,4 +271,289 @@ describe('Unit: Prebid Module', function () { assert.ok(spyLogError.calledWith(error), 'expected error was logged'); }); }); + + describe('requestBids', () => { + it('should add bidsBackHandler callback to bidmanager', () => { + var spyAddOneTimeCallBack = sinon.spy(bidmanager, 'addOneTimeCallback'); + var requestObj = { + bidsBackHandler: function bidsBackHandlerCallback() {} + }; + pbjs.requestBids(requestObj); + assert.ok(spyAddOneTimeCallBack.calledWith(requestObj.bidsBackHandler), + 'called bidmanager.addOneTimeCallback'); + bidmanager.addOneTimeCallback.restore(); + }); + + it('should log message when adUnits not configured', () => { + const logMessageSpy = sinon.spy(utils, 'logMessage'); + const adUnitsBackup = pbjs.adUnits; + + pbjs.adUnits = []; + pbjs.requestBids({}); + + assert.ok(logMessageSpy.calledWith('No adUnits configured. No bids requested.'), 'expected message was logged'); + utils.logMessage.restore(); + pbjs.adUnits = adUnitsBackup; + }); + + it('should execute callback after timeout', () => { + var spyExecuteCallback = sinon.spy(bidmanager, 'executeCallback'); + var clock = sinon.useFakeTimers(); + var requestObj = { + bidsBackHandler: function bidsBackHandlerCallback() {}, + timeout: 2000 + }; + + pbjs.requestBids(requestObj); + + clock.tick(requestObj.timeout - 1); + assert.ok(spyExecuteCallback.notCalled, 'bidmanager.executeCallback not called'); + + clock.tick(1); + assert.ok(spyExecuteCallback.called, 'called bidmanager.executeCallback'); + + bidmanager.executeCallback.restore(); + clock.restore(); + }); + + it('should call callBids function on adaptermanager', () => { + var spyCallBids = sinon.spy(adaptermanager, 'callBids'); + pbjs.requestBids({}); + assert.ok(spyCallBids.called, 'called adaptermanager.callBids'); + adaptermanager.callBids.restore(); + }); + }); + + describe('onEvent', () => { + it('should log an error when handler is not a function', () => { + var spyLogError = sinon.spy(utils, 'logError'); + var event = 'testEvent'; + pbjs.onEvent(event); + assert.ok(spyLogError.calledWith('The event handler provided is not a function and was not set on event "' + event + '".'), + 'expected error was logged'); + utils.logError.restore(); + }); + + it('should log an error when id provided is not valid for event', () => { + var spyLogError = sinon.spy(utils, 'logError'); + var event = 'bidWon'; + pbjs.onEvent(event, Function, 'testId'); + assert.ok(spyLogError.calledWith('The id provided is not valid for event "' + event + '" and no handler was set.'), + 'expected error was logged'); + utils.logError.restore(); + }); + + it('should call events.on with valid parameters', () => { + var spyEventsOn = sinon.spy(events, 'on'); + pbjs.onEvent('bidWon', Function); + assert.ok(spyEventsOn.calledWith('bidWon', Function)); + events.on.restore(); + }); + }); + + describe('offEvent', () => { + it('should return when id provided is not valid for event', () => { + var spyEventsOff = sinon.spy(events, 'off'); + pbjs.offEvent('bidWon', Function, 'testId'); + assert.ok(spyEventsOff.notCalled); + events.off.restore(); + }); + + it('should call events.off with valid parameters', () => { + var spyEventsOff = sinon.spy(events, 'off'); + pbjs.offEvent('bidWon', Function); + assert.ok(spyEventsOff.calledWith('bidWon', Function)); + events.off.restore(); + }); + }); + + describe('addCallback', () => { + it('should log error and return null id when error registering callback', () => { + var spyLogError = sinon.spy(utils, 'logError'); + var id = pbjs.addCallback('event', 'fakeFunction'); + assert.equal(id, null, 'id returned was null'); + assert.ok(spyLogError.calledWith('error registering callback. Check method signature'), + 'expected error was logged'); + utils.logError.restore(); + }); + + it('should add callback to bidmanager', () => { + var spyAddCallback = sinon.spy(bidmanager, 'addCallback'); + var id = pbjs.addCallback('event', Function); + assert.ok(spyAddCallback.calledWith(id, Function, 'event'), 'called bidmanager.addCallback'); + bidmanager.addCallback.restore(); + }); + }); + + describe('removeCallback', () => { + it('should return null', () => { + const id = pbjs.removeCallback(); + assert.equal(id, null); + }); + }); + + describe('registerBidAdapter', () => { + it('should register bidAdaptor with adaptermanager', () => { + var registerBidAdapterSpy = sinon.spy(adaptermanager, 'registerBidAdapter'); + pbjs.registerBidAdapter(Function, 'biddercode'); + assert.ok(registerBidAdapterSpy.called, 'called adaptermanager.registerBidAdapter'); + adaptermanager.registerBidAdapter.restore(); + }); + + it('should catch thrown errors', () => { + var spyLogError = sinon.spy(utils, 'logError'); + var errorObject = {message: 'bidderAdaptor error'}; + var bidderAdaptor = sinon.stub().throws(errorObject); + + pbjs.registerBidAdapter(bidderAdaptor, 'biddercode'); + + var errorMessage = 'Error registering bidder adapter : ' + errorObject.message; + assert.ok(spyLogError.calledWith(errorMessage), 'expected error was caught'); + utils.logError.restore(); + }); + }); + + describe('bidsAvailableForAdapter', () => { + it('should update requested bid with status set to available', () => { + const bidderCode = 'appnexus'; + pbjs.bidsAvailableForAdapter(bidderCode); + + const requestedBids = pbjs._bidsRequested.find(bid => bid.bidderCode === bidderCode); + requestedBids.bids.forEach(bid => { + assert.equal(bid.bidderCode, bidderCode, 'bidderCode was set'); + assert.equal(bid.statusMessage, 'Bid available', 'bid set as available'); + }); + }); + }); + + describe('createBid', () => { + it('should return a bid object', () => { + const statusCode = 1; + const bid = pbjs.createBid(statusCode); + assert.isObject(bid, 'bid is an object'); + assert.equal(bid.getStatusCode(), statusCode, 'bid has correct status'); + + const defaultStatusBid = pbjs.createBid(); + assert.isObject(defaultStatusBid, 'bid is an object'); + assert.equal(defaultStatusBid.getStatusCode(), 0, 'bid has correct status'); + }); + }); + + describe('addBidResponse', () => { + it('should call bidmanager.addBidResponse', () => { + const addBidResponseStub = sinon.stub(bidmanager, 'addBidResponse'); + const adUnitCode = 'testcode'; + const bid = pbjs.createBid(0); + + pbjs.addBidResponse(adUnitCode, bid); + assert.ok(addBidResponseStub.calledWith(adUnitCode, bid), 'called bidmanager.addBidResponse'); + bidmanager.addBidResponse.restore(); + }); + }); + + describe('loadScript', () => { + it('should call adloader.loadScript', () => { + const loadScriptSpy = sinon.spy(adloader, 'loadScript'); + const tagSrc = 'testsrc'; + const callback = Function; + const useCache = false; + + pbjs.loadScript(tagSrc, callback, useCache); + assert.ok(loadScriptSpy.calledWith(tagSrc, callback, useCache), 'called adloader.loadScript'); + adloader.loadScript.restore(); + }); + }); + + describe('enableAnalytics', () => { + let logErrorSpy; + + beforeEach(() => { + logErrorSpy = sinon.spy(utils, 'logError'); + }); + + afterEach(() => { + utils.logError.restore(); + }); + + it('should log error when not passed options', () => { + const error = 'pbjs.enableAnalytics should be called with option {}'; + pbjs.enableAnalytics(); + assert.ok(logErrorSpy.calledWith(error), 'expected error was logged'); + }); + + it('should call ga.enableAnalytics with options', () => { + const enableAnalyticsSpy = sinon.spy(ga, 'enableAnalytics'); + const options = {'provider': 'ga'}; + const error = 'pbjs.enableAnalytics should be called with option {}'; + + pbjs.enableAnalytics(options); + ga.enableAnalytics.restore(); + }); + + it('should catch errors thrown from ga.enableAnalytics', () => { + const error = {message: 'Error calling GA: '}; + const enableAnalyticsStub = sinon.stub(ga, 'enableAnalytics').throws(error); + const options = {'provider': 'ga'}; + + pbjs.enableAnalytics(options); + assert.ok(logErrorSpy.calledWith(error.message), 'expected error was caught'); + ga.enableAnalytics.restore(); + }); + + it('should return null for other providers', () => { + const options = {'provider': 'other_provider'}; + const returnValue = pbjs.enableAnalytics(options); + assert.equal(returnValue, null, 'expected return value'); + }); + }); + + describe('sendTimeoutEvent', () => { + it('should emit BID_TIMEOUT for timed out bids', () => { + const eventsEmitSpy = sinon.spy(events, 'emit'); + pbjs.sendTimeoutEvent(); + assert.ok(eventsEmitSpy.calledWith(CONSTANTS.EVENTS.BID_TIMEOUT), 'emitted events BID_TIMEOUT'); + events.emit.restore(); + }); + }); + + describe('aliasBidder', () => { + it('should call adaptermanager.aliasBidder', () => { + const aliasBidAdapterSpy = sinon.spy(adaptermanager, 'aliasBidAdapter'); + const bidderCode = 'testcode'; + const alias = 'testalias'; + + pbjs.aliasBidder(bidderCode, alias); + assert.ok(aliasBidAdapterSpy.calledWith(bidderCode, alias), 'called adaptermanager.aliasBidAdapterSpy'); + adaptermanager.aliasBidAdapter.restore(); + }); + + it('should log error when not passed correct arguments', () => { + const logErrorSpy = sinon.spy(utils, 'logError'); + const error = 'bidderCode and alias must be passed as arguments'; + + pbjs.aliasBidder(); + assert.ok(logErrorSpy.calledWith(error), 'expected error was logged'); + utils.logError.restore(); + }); + }); + + describe('setPriceGranularity', () => { + it('should log error when not passed granularity', () => { + const logErrorSpy = sinon.spy(utils, 'logError'); + const error = 'Prebid Error: no value passed to `setPriceGranularity()`'; + + pbjs.setPriceGranularity(); + assert.ok(logErrorSpy.calledWith(error), 'expected error was logged'); + utils.logError.restore(); + }); + + it('should call bidmanager.setPriceGranularity with granularity', () => { + const setPriceGranularitySpy = sinon.spy(bidmanager, 'setPriceGranularity'); + const granularity = 'low'; + + pbjs.setPriceGranularity(granularity); + assert.ok(setPriceGranularitySpy.called, 'called bidmanager.setPriceGranularity'); + bidmanager.setPriceGranularity.restore(); + }); + }); }); From 4710436cf30b96f3be01f430ead19e22c23e7492 Mon Sep 17 00:00:00 2001 From: Nate Guisinger Date: Thu, 19 May 2016 17:01:18 -0700 Subject: [PATCH 120/160] check both GPT getAdUnitPath and getSlotElementId (#364) * check both GPT getAdUnitPath and getSlotElementId for matching targeting config * log messasge on set GPT slot targeting --- src/prebid.js | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/prebid.js b/src/prebid.js index dd13cfd4ea5..107a8759085 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -134,7 +134,7 @@ function getWinningBidTargeting() { }; }); - if(presets) { + if (presets) { winners.concat(presets); } @@ -273,7 +273,7 @@ pbjs.getBidResponses = function () { pbjs.getBidResponsesForAdUnitCode = function (adUnitCode) { const bids = pbjs._bidsReceived.filter(bid => bid.adUnitCode === adUnitCode); return { - bids : bids + bids: bids }; }; @@ -287,14 +287,21 @@ pbjs.setTargetingForGPTAsync = function () { utils.logError('window.googletag is not defined on the page'); return; } + window.googletag.pubads().getSlots().forEach(slot => { getAllTargeting() - .filter(targeting => Object.keys(targeting)[0] === slot.getAdUnitPath()) - .forEach(targeting => targeting[Object.keys(targeting)[0]].forEach(key => { - key[Object.keys(key)[0]].forEach(value => slot.setTargeting(Object.keys(key)[0], value)); - })); + .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, index, array) => { + 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)); + })); }); - }; /** From 9a82232accf12ca91cbace018bcec9bb47dcae17 Mon Sep 17 00:00:00 2001 From: Chris Naegelin Date: Fri, 20 May 2016 16:10:45 +0200 Subject: [PATCH 121/160] RTK Aardvark Header Bidding Adapter --- integrationExamples/gpt/pbjs_example_gpt.html | 14 +++ package.json | 1 + src/adapters/aardvark.js | 106 ++++++++++++++++++ 3 files changed, 121 insertions(+) create mode 100644 src/adapters/aardvark.js diff --git a/integrationExamples/gpt/pbjs_example_gpt.html b/integrationExamples/gpt/pbjs_example_gpt.html index 5384bf6cc0a..5448c58f2f1 100644 --- a/integrationExamples/gpt/pbjs_example_gpt.html +++ b/integrationExamples/gpt/pbjs_example_gpt.html @@ -81,6 +81,20 @@ params: { placementId: 'TO ADD' } + }, { + bidder:"aardvark", + params: { + ai: "TO ADD", + sc: "TO ADD" + } + }, { + bidder: 'pulsepoint', + params: { + "cf": "728X90", + "cp": "558467", + "ct": "317348" + } + rtkid: "6372" }, { bidder: 'pubmatic', params: { diff --git a/package.json b/package.json index 5965b0aa6d5..fe044a7a1ef 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "url" : "https://github.com/prebid/Prebid.js.git" }, "adapters": [ + "aardvark", "adequant", "adform", "aol", diff --git a/src/adapters/aardvark.js b/src/adapters/aardvark.js new file mode 100644 index 00000000000..145b72d9ec7 --- /dev/null +++ b/src/adapters/aardvark.js @@ -0,0 +1,106 @@ +var utils = require('../utils.js'); +var bidfactory = require('../bidfactory.js'); +var bidmanager = require('../bidmanager.js'); +var adloader = require('../adloader'); + + +/** + * Adapter for requesting bids from RTK Aardvark + * To request an RTK Aardvark Header bidding account + * or for additional integration support please contact sales@rtk.io + */ + +var AardvarkAdapter = function AardvarkAdapter() { + + function _callBids(params) { + var rtkBids = params.bids || []; + + _requestBids(rtkBids); + } + + function _requestBids(bidReqs) { + // build bid request object + var ref = window.top.location.host; + var ai = ""; + var shortcodes = []; + + //build bid URL for RTK + utils._each(bidReqs, function (bid) { + ai = utils.getBidIdParamater('ai', bid.params); + var sc = utils.getBidIdParamater('sc', bid.params); + shortcodes.push(sc); + }); + + var scURL = ""; + + if (shortcodes.length > 1) { + scURL = shortcodes.join("_"); + } else { + scURL = shortcodes[0]; + } + + var scriptUrl = '//thor.rtk.io/' + ai + "/" + scURL + "/aardvark/?jsonp=window.pbjs.aardvarkResponse&rtkreferer=" + ref; + adloader.loadScript(scriptUrl, null); + } + + //expose the callback to the global object: + window.pbjs.aardvarkResponse = function (rtkResponseObj) { + + //Get all initial Aardvark Bid Objects + var bidsObj = pbjs._bidsRequested.filter(function (bidder) { + return bidder.bidderCode === 'aardvark'; + })[0]; + + var returnedBidIDs = {}; + var placementIDmap = {}; + + if (rtkResponseObj.length > 0) { + rtkResponseObj.forEach(function (bid) { + + if (!bid.error) { + var currentBid = bidsObj.bids.filter(function (r) { + return r.params.sc === bid.id; + })[0]; + if (currentBid) { + var bidResponse = bidfactory.createBid(1); + bidResponse.bidderCode = "aardvark"; + bidResponse.cpm = bid.cpm; + bidResponse.ad = bid.adm; + bidResponse.ad += utils.createTrackPixelHtml(decodeURIComponent(bid.nurl)); + bidResponse.width = currentBid.sizes[0][0]; + bidResponse.height = currentBid.sizes[0][1]; + returnedBidIDs[bid.id] = currentBid.placementCode; + bidmanager.addBidResponse(currentBid.placementCode, bidResponse); + } + + } + + }); + + } + + //All bids are back - lets add a bid response for anything that did not receive a bid. + var initialSC = []; + bidsObj.bids.forEach(function (bid) { + initialSC.push(bid.params.sc); + placementIDmap[bid.params.sc] = bid.placementCode; + }); + + let difference = initialSC.filter(x => Object.keys(returnedBidIDs).indexOf(x) === -1); + + difference.forEach(function (shortcode) { + var bidResponse = bidfactory.createBid(2); + var placementcode = placementIDmap[shortcode]; + bidResponse.bidderCode = "aardvark"; + bidmanager.addBidResponse(placementcode, bidResponse); + }); + + + }; // aardvarkResponse + + return { + callBids: _callBids + }; +}; + +module.exports = AardvarkAdapter; From c705d241e973dd23d131197f9d1e26674b2c3356 Mon Sep 17 00:00:00 2001 From: Chris Naegelin Date: Fri, 20 May 2016 16:23:33 +0200 Subject: [PATCH 122/160] no message --- integrationExamples/gpt/pbjs_example_gpt.html | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/integrationExamples/gpt/pbjs_example_gpt.html b/integrationExamples/gpt/pbjs_example_gpt.html index 5448c58f2f1..5691d24be11 100644 --- a/integrationExamples/gpt/pbjs_example_gpt.html +++ b/integrationExamples/gpt/pbjs_example_gpt.html @@ -87,15 +87,7 @@ ai: "TO ADD", sc: "TO ADD" } - }, { - bidder: 'pulsepoint', - params: { - "cf": "728X90", - "cp": "558467", - "ct": "317348" - } - rtkid: "6372" - }, { + }, { bidder: 'pubmatic', params: { publisherId: 'TO ADD', From 6143a5b15fc310e57ca9da8b8ff744d1ec0e2b01 Mon Sep 17 00:00:00 2001 From: Chris Naegelin Date: Fri, 20 May 2016 17:12:20 +0200 Subject: [PATCH 123/160] rtk sample --- integrationExamples/gpt/pbjs_example_rtk.html | 320 ++++++++++++++++++ 1 file changed, 320 insertions(+) create mode 100644 integrationExamples/gpt/pbjs_example_rtk.html diff --git a/integrationExamples/gpt/pbjs_example_rtk.html b/integrationExamples/gpt/pbjs_example_rtk.html new file mode 100644 index 00000000000..4dba15bebf1 --- /dev/null +++ b/integrationExamples/gpt/pbjs_example_rtk.html @@ -0,0 +1,320 @@ + + + + + + + + + + + + +

    Prebid.js Test

    + +
    + +
    + + +
    + +
    + + + + + + + From 634536c61bc7d43fe8fe6d965eb238af3c1b53c1 Mon Sep 17 00:00:00 2001 From: protonate Date: Fri, 20 May 2016 09:46:41 -0700 Subject: [PATCH 124/160] Prebid v0.9.1 release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0c04529e4b9..937e29fb803 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "0.9.0", + "version": "0.9.1", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From c6bda7a4f50d5fe3312cd03c71fe46bc3a5b2deb Mon Sep 17 00:00:00 2001 From: Matt Lane Date: Fri, 20 May 2016 15:28:52 -0700 Subject: [PATCH 125/160] Assert ga.enableAnalytics called with options (#367) Fixes pbjs api unit test. --- test/spec/unit/pbjs_api_spec.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index 50e2949a426..83ca498731d 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -483,10 +483,15 @@ describe('Unit: Prebid Module', function () { it('should call ga.enableAnalytics with options', () => { const enableAnalyticsSpy = sinon.spy(ga, 'enableAnalytics'); - const options = {'provider': 'ga'}; - const error = 'pbjs.enableAnalytics should be called with option {}'; + let options = {'provider': 'ga'}; pbjs.enableAnalytics(options); + assert.ok(enableAnalyticsSpy.calledWith({}), 'ga.enableAnalytics called with empty options object'); + + options['options'] = 'testoptions'; + pbjs.enableAnalytics(options); + assert.ok(enableAnalyticsSpy.calledWith(options.options), 'ga.enableAnalytics called with provided options'); + ga.enableAnalytics.restore(); }); From ac6f5bfed5b67dd133396ce1a74e06fb0281998f Mon Sep 17 00:00:00 2001 From: Chris Naegelin Date: Tue, 24 May 2016 10:33:40 +0200 Subject: [PATCH 126/160] Requested changes --- integrationExamples/gpt/pbjs_example_gpt.html | 2 ++ src/adapters/aardvark.js | 12 +++++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/integrationExamples/gpt/pbjs_example_gpt.html b/integrationExamples/gpt/pbjs_example_gpt.html index 5691d24be11..6cc9449c984 100644 --- a/integrationExamples/gpt/pbjs_example_gpt.html +++ b/integrationExamples/gpt/pbjs_example_gpt.html @@ -84,7 +84,9 @@ }, { bidder:"aardvark", params: { + //The RTK Auction ID ai: "TO ADD", + //The RTK Ad Unit ID (shortcode) sc: "TO ADD" } }, { diff --git a/src/adapters/aardvark.js b/src/adapters/aardvark.js index 145b72d9ec7..3fde05edb27 100644 --- a/src/adapters/aardvark.js +++ b/src/adapters/aardvark.js @@ -19,8 +19,14 @@ var AardvarkAdapter = function AardvarkAdapter() { } function _requestBids(bidReqs) { - // build bid request object - var ref = window.top.location.host; + + try { + var ref = window.top.location.host; + } + catch (err) { + var ref = "thor.rtk.io"; + + } var ai = ""; var shortcodes = []; @@ -40,7 +46,7 @@ var AardvarkAdapter = function AardvarkAdapter() { } var scriptUrl = '//thor.rtk.io/' + ai + "/" + scURL + "/aardvark/?jsonp=window.pbjs.aardvarkResponse&rtkreferer=" + ref; - adloader.loadScript(scriptUrl, null); + adloader.loadScript(scriptUrl); } //expose the callback to the global object: From e88f61f4fdee09a3228951447980e4e9eecf7e9a Mon Sep 17 00:00:00 2001 From: Chris Naegelin Date: Tue, 24 May 2016 10:46:39 +0200 Subject: [PATCH 127/160] unnecessary file --- integrationExamples/gpt/pbjs_example_rtk.html | 320 ------------------ 1 file changed, 320 deletions(-) delete mode 100644 integrationExamples/gpt/pbjs_example_rtk.html diff --git a/integrationExamples/gpt/pbjs_example_rtk.html b/integrationExamples/gpt/pbjs_example_rtk.html deleted file mode 100644 index 4dba15bebf1..00000000000 --- a/integrationExamples/gpt/pbjs_example_rtk.html +++ /dev/null @@ -1,320 +0,0 @@ - - - - - - - - - - - - -

    Prebid.js Test

    - -
    - -
    - - -
    - -
    - - - - - - - From f450d8fe603469d555babfad8254524fb473db4d Mon Sep 17 00:00:00 2001 From: Matt Kendall Date: Tue, 24 May 2016 13:13:54 -0400 Subject: [PATCH 128/160] Fix for #368 (#369) --- .babelrc | 5 ++++- package.json | 19 ++++++++++++++----- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/.babelrc b/.babelrc index 9d8d5165620..ed7067c357b 100644 --- a/.babelrc +++ b/.babelrc @@ -1 +1,4 @@ -{ "presets": ["es2015"] } +{ + "presets": ["es2015"], + "plugins": ["transform-object-assign"] +} diff --git a/package.json b/package.json index 937e29fb803..c435caacc0c 100644 --- a/package.json +++ b/package.json @@ -6,9 +6,9 @@ "scripts": { "test": "gulp test" }, - "repository" : { - "type" : "git", - "url" : "https://github.com/prebid/Prebid.js.git" + "repository": { + "type": "git", + "url": "https://github.com/prebid/Prebid.js.git" }, "adapters": [ "adequant", @@ -27,14 +27,23 @@ "yieldbot", "nginad", "brightcom", - { "appnexus": {"alias": "brealtime"} }, - { "appnexus": {"alias": "pagescience"} } + { + "appnexus": { + "alias": "brealtime" + } + }, + { + "appnexus": { + "alias": "pagescience" + } + } ], "author": "the prebid.js contributors", "license": "Apache-2.0", "devDependencies": { "babel-core": "^6.5.2", "babel-loader": "^6.2.3", + "babel-plugin-transform-object-assign": "^6.8.0", "babel-preset-es2015": "^6.5.0", "block-loader": "^2.1.0", "chai": "^3.3.0", From 30f77c00e2d3f041ce49f3f003e8ec8795df2fd3 Mon Sep 17 00:00:00 2001 From: Matt Kendall Date: Wed, 25 May 2016 09:25:52 -0400 Subject: [PATCH 129/160] Restore functionality in IE with polyfill (#373) * Restore functionality in IE with polyfill * Update module name --- src/polyfill.js | 26 ++++++++++++++++++++++++++ src/prebid.js | 1 + 2 files changed, 27 insertions(+) create mode 100644 src/polyfill.js diff --git a/src/polyfill.js b/src/polyfill.js new file mode 100644 index 00000000000..4173495ad3e --- /dev/null +++ b/src/polyfill.js @@ -0,0 +1,26 @@ +/** @module polyfill +Misc polyfills +*/ + +if (!Array.prototype.find) { + Array.prototype.find = function(predicate) { + if (this === null) { + throw new TypeError('Array.prototype.find called on null or undefined'); + } + if (typeof predicate !== 'function') { + throw new TypeError('predicate must be a function'); + } + var list = Object(this); + var length = list.length >>> 0; + var thisArg = arguments[1]; + var value; + + for (var i = 0; i < length; i++) { + value = list[i]; + if (predicate.call(thisArg, value, i, list)) { + return value; + } + } + return undefined; + }; +} diff --git a/src/prebid.js b/src/prebid.js index 107a8759085..421c6cd8086 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -1,6 +1,7 @@ /** @module pbjs */ import { flatten, uniques, getKeys, isGptPubadsDefined } from './utils'; +import 'polyfill'; // if pbjs already exists in global document scope, use it, if not, create the object window.pbjs = (window.pbjs || {}); From a5c3775aa2e706c9ef6e4d279618eafe717934fb Mon Sep 17 00:00:00 2001 From: Matt Kendall Date: Thu, 26 May 2016 14:48:59 -0400 Subject: [PATCH 130/160] Fix issue with nginead response not being registered. (#380) * Fix issue with nginead response not being registered. * fix issue with matching imp.id * Unused --- src/adapters/nginad.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/adapters/nginad.js b/src/adapters/nginad.js index 596dd9de134..40bfe875654 100644 --- a/src/adapters/nginad.js +++ b/src/adapters/nginad.js @@ -66,6 +66,7 @@ var NginAdAdapter = function NginAdAdapter() { //assign the first adUnit (placement) for bad bids; defaultPlacementForBadBid = bidReqs[0].placementCode; + //build impression array for nginad utils._each(bidReqs, function(bid) { var tagId = utils.getBidIdParamater('pzoneid', bid.params); @@ -74,7 +75,7 @@ var NginAdAdapter = function NginAdAdapter() { var whArr = getWidthAndHeight(bid); var imp = { - id: utils.getUniqueIdentifierStr(), + id: bid.bidId, banner: { w: whArr[0], h: whArr[1] @@ -142,7 +143,7 @@ var NginAdAdapter = function NginAdAdapter() { // try to fetch the bid request we sent NginAd var bidObj = pbjs._bidsRequested.find(bidSet => bidSet.bidderCode === 'nginad').bids - .filter(bid => bid.params && bid.params.impId === id); + .find(bid => bid.bidId === id); if (!bidObj) { return handleErrorResponse(nginadBid, defaultPlacementForBadBid); } From a2ef68efe9edd8121799c82ee01667175008e95b Mon Sep 17 00:00:00 2001 From: Matt Kendall Date: Thu, 26 May 2016 15:49:39 -0400 Subject: [PATCH 131/160] Carson banov feature 120112151 handle tie (#381) * Provide deterministic behavior for sort function. [#120112151] * Triple equal. [#120112151] * Export and unit test the getHighestCpm function. [#120112151] * Move the getHighestCpm function to utils. --- src/prebid.js | 10 ++++------ src/utils.js | 7 +++++++ test/spec/utils_spec.js | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 6 deletions(-) diff --git a/src/prebid.js b/src/prebid.js index 421c6cd8086..60bd3804c99 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -1,6 +1,6 @@ /** @module pbjs */ -import { flatten, uniques, getKeys, isGptPubadsDefined } from './utils'; +import { flatten, uniques, getKeys, isGptPubadsDefined, getHighestCpm } from './utils'; import 'polyfill'; // if pbjs already exists in global document scope, use it, if not, create the object @@ -101,6 +101,7 @@ function checkDefinedPlacement(id) { return true; } + function getWinningBidTargeting() { let presets; if (isGptPubadsDefined()) { @@ -123,7 +124,8 @@ function getWinningBidTargeting() { { adUnitCode: adUnitCode, cpm: 0, - adserverTargeting: {} + adserverTargeting: {}, + timeToRespond : 0 })); winners = winners.map(winner => { @@ -140,10 +142,6 @@ function getWinningBidTargeting() { } return winners; - - function getHighestCpm(previous, current) { - return previous.cpm < current.cpm ? current : previous; - } } function getBidLandscapeTargeting() { diff --git a/src/utils.js b/src/utils.js index 3b8cba0c6c8..d2a682de425 100644 --- a/src/utils.js +++ b/src/utils.js @@ -487,3 +487,10 @@ export function isGptPubadsDefined() { return true; } } + +export function getHighestCpm(previous, current) { + if (previous.cpm === current.cpm) { + return previous.timeToRespond > current.timeToRespond ? current : previous; + } + return previous.cpm < current.cpm ? current : previous; +} diff --git a/test/spec/utils_spec.js b/test/spec/utils_spec.js index bf9fb5e3262..213580458fd 100755 --- a/test/spec/utils_spec.js +++ b/test/spec/utils_spec.js @@ -489,4 +489,42 @@ describe('Utils', function () { }); }); + describe('getHighestCpm', function () { + it('should pick the existing highest cpm', function () { + var previous = { + cpm: 2, + timeToRespond: 100 + }; + var current = { + cpm: 1, + timeToRespond: 100 + }; + assert.equal(utils.getHighestCpm(previous, current), previous); + }); + + it('should pick the new highest cpm', function () { + var previous = { + cpm: 1, + timeToRespond: 100 + }; + var current = { + cpm: 2, + timeToRespond: 100 + }; + assert.equal(utils.getHighestCpm(previous, current), current); + }); + + it('should pick the fastest cpm in case of tie', function () { + var previous = { + cpm: 1, + timeToRespond: 100 + }; + var current = { + cpm: 1, + timeToRespond: 50 + }; + assert.equal(utils.getHighestCpm(previous, current), current); + }); + }); + }); From dd17780a56922b54fb0d93de8973f7b442675d40 Mon Sep 17 00:00:00 2001 From: Nate Guisinger Date: Fri, 27 May 2016 08:47:19 -0700 Subject: [PATCH 132/160] use bid request bidId for sovrn imp.id (#379) * use bid request bidId for sovrn imp.id * fixed placement undefined --- src/adapters/sovrn.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/adapters/sovrn.js b/src/adapters/sovrn.js index be3b90bcf4a..b1e38615f74 100644 --- a/src/adapters/sovrn.js +++ b/src/adapters/sovrn.js @@ -46,7 +46,7 @@ var SovrnAdapter = function SovrnAdapter() { var imp = { - id: utils.getUniqueIdentifierStr(), + id: bid.bidId, banner: { w: adW, h: adH @@ -105,7 +105,9 @@ var SovrnAdapter = function SovrnAdapter() { var bid = {}; // try to fetch the bid request we sent Sovrn - var bidObj = pbjs._bidsRequested.map(bidSet => bidSet.bids.filter(bid => bid.params && bid.params.impId === id)); + var bidObj = pbjs._bidsRequested.find(bidSet => bidSet.bidderCode === 'sovrn').bids + .find(bid => bid.bidId === id); + if (bidObj) { placementCode = bidObj.placementCode; placementsWithBidsBack.push(placementCode); From 19c60ee4a7d90b338d1eb09de7210f74d2a65480 Mon Sep 17 00:00:00 2001 From: Nate Guisinger Date: Tue, 31 May 2016 05:24:13 -0700 Subject: [PATCH 133/160] use refactored data structures (#382) --- src/adapters/yieldbot.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/adapters/yieldbot.js b/src/adapters/yieldbot.js index 47f241acfaa..88c290d6ddb 100644 --- a/src/adapters/yieldbot.js +++ b/src/adapters/yieldbot.js @@ -96,9 +96,7 @@ var YieldbotAdapter = function YieldbotAdapter() { yieldbot.pub(psn); yieldbot.defineSlot(slot, { sizes: bid.sizes || [] }); - var cbId = utils.getUniqueIdentifierStr(); - bidmanager.pbCallbackMap[cbId] = bid; - ybotlib.definedSlots.push(cbId); + ybotlib.definedSlots.push(bid.bidId); }); yieldbot.enableAsync(); @@ -126,7 +124,9 @@ var YieldbotAdapter = function YieldbotAdapter() { var placementCode; var adapterConfig; - adapterConfig = bidmanager.getPlacementIdByCBIdentifer(v) || {}; + adapterConfig = pbjs._bidsRequested + .find(bidderRequest => bidderRequest.bidderCode === 'yieldbot').bids + .find(bid => bid.bidId === v) || {}; slot = adapterConfig.params.slot || ''; criteria = yieldbot.getSlotCriteria(slot); From bf06c0b0c49e2232ce85fbfa46f8279a39039c01 Mon Sep 17 00:00:00 2001 From: Nate Guisinger Date: Tue, 31 May 2016 05:31:00 -0700 Subject: [PATCH 134/160] Restore return format of bidsBackHandler callback (#371) * a `groupByPlacement` reduce function transforms the bids received array to a map * prevent concurrent bid requests If an auction is running don't accept a new request for bids.. This adds a `clearAuction` function to set `auctionRunning` false and to clear `_bidsRequested` and `_bidsReceived`. This is a stopgap measure util #353 * clear data structures on additional bid request * make auctionRunning private and allow passing adUnits to requestBids * pass adUnitCodes on refresh bids * use adUnitCodes if passed in to requestBids, otherwise `getBidderCodes()`, disambiguate `bidmanager.getBidderCode` * restore use of adUnitCodes to filter adUnits * cherrypick reset targeting merged * clear targeting fixes and test mock * Add GptPubadsDefined check --- .gitignore | 1 + src/adaptermanager.js | 10 ++-- src/bidmanager.js | 33 ++++++++++-- src/prebid.js | 92 +++++++++++++++++++++++---------- test/spec/unit/pbjs_api_spec.js | 14 +++++ 5 files changed, 115 insertions(+), 35 deletions(-) diff --git a/.gitignore b/.gitignore index 1e4d5248000..c0a1a2f8ba4 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ build # Test Files test/app gpt.html +gpt-each-bidder3.html # Dev File diff --git a/src/adaptermanager.js b/src/adaptermanager.js index 1b9e4d12758..0243ec11c00 100644 --- a/src/adaptermanager.js +++ b/src/adaptermanager.js @@ -10,8 +10,8 @@ import { BaseAdapter } from './adapters/baseAdapter'; var _bidderRegistry = {}; exports.bidderRegistry = _bidderRegistry; -function getBids({ bidderCode, requestId, bidderRequestId }) { - return pbjs.adUnits.map(adUnit => { +function getBids({ bidderCode, requestId, bidderRequestId, adUnits }) { + return adUnits.map(adUnit => { return adUnit.bids.filter(bid => bid.bidder === bidderCode) .map(bid => Object.assign(bid, { placementCode: adUnit.code, @@ -23,10 +23,10 @@ function getBids({ bidderCode, requestId, bidderRequestId }) { }).reduce(flatten, []); } -exports.callBids = () => { +exports.callBids = ({ adUnits }) => { const requestId = utils.getUniqueIdentifierStr(); - getBidderCodes().forEach(bidderCode => { + getBidderCodes(adUnits).forEach(bidderCode => { const adapter = _bidderRegistry[bidderCode]; if (adapter) { const bidderRequestId = utils.getUniqueIdentifierStr(); @@ -34,7 +34,7 @@ exports.callBids = () => { bidderCode, requestId, bidderRequestId, - bids: getBids({ bidderCode, requestId, bidderRequestId }), + bids: getBids({ bidderCode, requestId, bidderRequestId, adUnits }), start: new Date().getTime() }; utils.logMessage(`CALLING BIDDER ======= ${bidderCode}`); diff --git a/src/bidmanager.js b/src/bidmanager.js index 7e053b83f60..e0f9adbe35e 100644 --- a/src/bidmanager.js +++ b/src/bidmanager.js @@ -22,7 +22,7 @@ const _hgPriceCap = 20.00; */ exports.getTimedOutBidders = function () { return pbjs._bidsRequested - .map(getBidderCodes) + .map(getBidderCode) .filter(uniques) .filter(bidder => pbjs._bidsReceived .map(getBidders) @@ -32,7 +32,7 @@ exports.getTimedOutBidders = function () { function timestamp() { return new Date().getTime(); } -function getBidderCodes(bidSet) { +function getBidderCode(bidSet) { return bidSet.bidderCode; } @@ -236,6 +236,8 @@ exports.executeCallback = function () { processCallbacks([externalOneTimeCallback]); externalOneTimeCallback = null; } + + pbjs.clearAuction(); }; function triggerAdUnitCallbacks(adUnitCode) { @@ -249,11 +251,36 @@ function processCallbacks(callbackQueue) { if (utils.isArray(callbackQueue)) { for (i = 0; i < callbackQueue.length; i++) { var func = callbackQueue[i]; - func.call(pbjs, pbjs._bidsReceived); + func.call(pbjs, pbjs._bidsReceived.reduce(groupByPlacement, {})); } } } +/** + * groupByPlacement is a reduce function that converts an array of Bid objects + * to an object with placement codes as keys, with each key representing an object + * with an array of `Bid` objects for that placement + * @param prev previous value as accumulator object + * @param item current array item + * @param idx current index + * @param arr the array being reduced + * @returns {*} as { [adUnitCode]: { bids: [Bid, Bid, Bid] } } + */ +function groupByPlacement(prev, item, idx, arr) { + // this uses a standard "array to map" operation that could be abstracted further + if (item.adUnitCode in Object.keys(prev)) { + // if the adUnitCode key is present in the accumulator object, continue + return prev; + } else { + // otherwise add the adUnitCode key to the accumulator object and set to an object with an + // array of Bids for that adUnitCode + prev[item.adUnitCode] = { + bids: arr.filter(bid => bid.adUnitCode === item.adUnitCode) + }; + return prev; + } +} + /** * Add a one time callback, that is discarded after it is called * @param {Function} callback [description] diff --git a/src/prebid.js b/src/prebid.js index 60bd3804c99..311ead9e640 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -26,6 +26,8 @@ var BID_TIMEOUT = CONSTANTS.EVENTS.BID_TIMEOUT; var pb_bidsTimedOut = false; var pb_sendAllBids = false; +var auctionRunning = false; +var presetTargeting = []; var eventValidators = { bidWon: checkDefinedPlacement @@ -35,6 +37,7 @@ var eventValidators = { pbjs._bidsRequested = []; pbjs._bidsReceived = []; +pbjs._adsReceived = []; //default timeout for all bids pbjs.bidderTimeout = pbjs.bidderTimeout || 2000; @@ -101,11 +104,35 @@ function checkDefinedPlacement(id) { return true; } +function resetPresetTargeting() { + if (isGptPubadsDefined()) { + window.googletag.pubads().getSlots().forEach(slot => { + slot.clearTargeting(); + }); + + setTargeting(presetTargeting); + } +} + +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 getWinningBidTargeting() { - let presets; if (isGptPubadsDefined()) { - presets = (function getPresetTargeting() { + presetTargeting = (function getPresetTargeting() { return window.googletag.pubads().getSlots().map(slot => { return { [slot.getAdUnitPath()]: slot.getTargetingKeys().map(key => { @@ -125,7 +152,7 @@ function getWinningBidTargeting() { adUnitCode: adUnitCode, cpm: 0, adserverTargeting: {}, - timeToRespond : 0 + timeToRespond: 0 })); winners = winners.map(winner => { @@ -137,8 +164,8 @@ function getWinningBidTargeting() { }; }); - if (presets) { - winners.concat(presets); + if (presetTargeting) { + winners.concat(presetTargeting); } return winners; @@ -189,12 +216,10 @@ pbjs.getAdserverTargetingForAdUnitCodeStr = function (adunitCode) { }; /** - * This function returns the query string targeting parameters available at this moment for a given ad unit. Note that some bidder's response may not have been received if you call this function too quickly after the requests are sent. - * @param {string} [adunitCode] adUnitCode to get the bid responses for - * @alias module:pbjs.getAdserverTargetingForAdUnitCode - * @return {object} returnObj return bids +* This function returns the query string targeting parameters available at this moment for a given ad unit. Note that some bidder's response may not have been received if you call this function too quickly after the requests are sent. + * @param adUnitCode {string} adUnitCode to get the bid responses for + * @returns {object} returnObj return bids */ - pbjs.getAdserverTargetingForAdUnitCode = function (adUnitCode) { utils.logInfo('Invoking pbjs.getAdserverTargetingForAdUnitCode', arguments); @@ -287,20 +312,7 @@ pbjs.setTargetingForGPTAsync = function () { return; } - window.googletag.pubads().getSlots().forEach(slot => { - getAllTargeting() - .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, index, array) => { - 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)); - })); - }); + setTargeting(getAllTargeting()); }; /** @@ -387,21 +399,47 @@ pbjs.removeAdUnit = function (adUnitCode) { } }; +pbjs.clearAuction = function() { + auctionRunning = false; + utils.logMessage('Prebid auction cleared'); +}; + /** * * @param bidsBackHandler * @param timeout + * @param adUnits + * @param adUnitCodes */ -pbjs.requestBids = function ({ bidsBackHandler, timeout }) { +pbjs.requestBids = function ({ bidsBackHandler, timeout, adUnits, adUnitCodes }) { + if (auctionRunning) { + utils.logError('Prebid Error: `pbjs.requestBids` was called while a previous auction was' + + ' still running. Resubmit this request.'); + return; + } else { + auctionRunning = true; + pbjs._bidsRequested = []; + pbjs._bidsReceived = []; + resetPresetTargeting(); + } + const cbTimeout = timeout || pbjs.bidderTimeout; + // use adUnits provided or from pbjs global + adUnits = adUnits || pbjs.adUnits; + + // if specific adUnitCodes filter adUnits for those codes + if (adUnitCodes && adUnitCodes.length) { + adUnits = adUnits.filter(adUnit => adUnitCodes.includes(adUnit.code)); + } + if (typeof bidsBackHandler === objectType_function) { bidmanager.addOneTimeCallback(bidsBackHandler); } utils.logInfo('Invoking pbjs.requestBids', arguments); - if (!pbjs.adUnits || pbjs.adUnits.length === 0) { + if (!adUnits || adUnits.length === 0) { utils.logMessage('No adUnits configured. No bids requested.'); return; } @@ -409,7 +447,7 @@ pbjs.requestBids = function ({ bidsBackHandler, timeout }) { //set timeout for all bids setTimeout(bidmanager.executeCallback, cbTimeout); - adaptermanager.callBids(); + adaptermanager.callBids({ adUnits, adUnitCodes }); }; /** diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index 83ca498731d..68ecc092200 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -19,6 +19,12 @@ pbjs = pbjs || {}; pbjs._bidsRequested = getBidRequests(); pbjs._bidsReceived = getBidResponses(); +function resetAuction() { + pbjs.clearAuction(); + pbjs._bidsRequested = getBidRequests(); + pbjs._bidsReceived = getBidResponses(); +} + var Slot = function Slot(elementId, pathId) { var slot = { getSlotElementId: function getSlotElementId() { @@ -38,6 +44,10 @@ var Slot = function Slot(elementId, pathId) { getTargetingKeys: function getTargetingKeys() { return ['testKey']; + }, + + clearTargeting: function clearTargeting() { + return googletag.pubads().getSlots(); } }; slot.spySetTargeting = sinon.spy(slot, 'setTargeting'); @@ -282,6 +292,7 @@ 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', () => { @@ -294,6 +305,7 @@ describe('Unit: Prebid Module', function () { assert.ok(logMessageSpy.calledWith('No adUnits configured. No bids requested.'), 'expected message was logged'); utils.logMessage.restore(); pbjs.adUnits = adUnitsBackup; + resetAuction(); }); it('should execute callback after timeout', () => { @@ -314,6 +326,7 @@ describe('Unit: Prebid Module', function () { bidmanager.executeCallback.restore(); clock.restore(); + resetAuction(); }); it('should call callBids function on adaptermanager', () => { @@ -321,6 +334,7 @@ describe('Unit: Prebid Module', function () { pbjs.requestBids({}); assert.ok(spyCallBids.called, 'called adaptermanager.callBids'); adaptermanager.callBids.restore(); + resetAuction(); }); }); From c84f75578b2a8ef1ea8de65f094cf6947214fa99 Mon Sep 17 00:00:00 2001 From: protonate Date: Tue, 31 May 2016 13:42:19 -0700 Subject: [PATCH 135/160] Prebid v0.9.2 Release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c435caacc0c..2df9883ffe2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "0.9.1", + "version": "0.9.2", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From c23edd08483867ce0a55d536170f6d88a069cd40 Mon Sep 17 00:00:00 2001 From: bjorn-wo Date: Fri, 3 Jun 2016 17:49:09 +0200 Subject: [PATCH 136/160] WideOrbit adapter (#332) * Added WideOrbit adapter * Fix User Matching code * Renamed adapter + some minor JS issues * Added flag for notifying front action is header bidding. Use returned bid. * Duplicated properties fixed. * Correcting adaptermanager after wrong merge. Now all tests passes correctly. * Seting fl and jscb directly in the pageImpression-part of the url, since they are hardcoded. * Fixed above the fold parameter * No need to modify original value. * Normalizing file regarding single or double quotes. Also, properly indenting. * Removed uneeded properties: referrer (twice) and tagId. * Slightly better readiblity for variable declarations (and definition in some cases). * Refactoring regarding function naming and the way we set rank parameter. Also making sure we encode/decode site and page parameters. * Ignoring TypeScript's typing definitions. * Added initial WideOrbit Adapter tests covering url-creation part. * Completed wide orbit adapter tests with the one regarding callback response. * Slightly better test names. * Fix support for already prepared tracking pixels * Modified indenting according to coding standard * Code review fixes * Marked required parameters * Remove unused library from package.json Fix unit tests after refactoring --- .gitignore | 3 + integrationExamples/gpt/pbjs_example_gpt.html | 7 + package.json | 3 + src/adapters/wideorbit.js | 213 ++++++++ test/spec/adapters/wideorbit_spec.js | 491 ++++++++++++++++++ 5 files changed, 717 insertions(+) create mode 100644 src/adapters/wideorbit.js create mode 100644 test/spec/adapters/wideorbit_spec.js diff --git a/.gitignore b/.gitignore index c0a1a2f8ba4..8486ee1a189 100644 --- a/.gitignore +++ b/.gitignore @@ -66,3 +66,6 @@ crashlytics-build.properties #zip files (for releases) *.zip + +# TypeScript typings +typings/ diff --git a/integrationExamples/gpt/pbjs_example_gpt.html b/integrationExamples/gpt/pbjs_example_gpt.html index 5384bf6cc0a..540f48be538 100644 --- a/integrationExamples/gpt/pbjs_example_gpt.html +++ b/integrationExamples/gpt/pbjs_example_gpt.html @@ -240,6 +240,13 @@ publisher_id: 5000563, // REQUIRED int or str publisher ID. To get one, register at https://control.adequant.com bidfloor: 0.01, // OPTIONAL float bid floor in $ CPM } + }, + { + bidder: 'wideorbit', + params: { + pbId: 123, // REQUIRED Publisher Id, + pId: 123456 // REQUIRED Placement Id + } } ] } diff --git a/package.json b/package.json index 2df9883ffe2..3e47727c1e2 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "yieldbot", "nginad", "brightcom", + "wideorbit", { "appnexus": { "alias": "brealtime" @@ -100,6 +101,7 @@ "mocha": "^1.21.4", "open": "0.0.5", "phantomjs": "^1.9.18", + "querystringify": "0.0.3", "raw-loader": "^0.5.1", "redis": "^0.12.1", "requirejs": "^2.1.20", @@ -107,6 +109,7 @@ "sinon": "^1.12.1", "string-replace-webpack-plugin": "0.0.3", "uglify-js": "^2.4.15", + "url-parse": "^1.0.5", "webpack": "^1.12.3", "webpack-stream": "^3.1.0", "yargs": "^1.3.1" diff --git a/src/adapters/wideorbit.js b/src/adapters/wideorbit.js new file mode 100644 index 00000000000..e8431cab7bf --- /dev/null +++ b/src/adapters/wideorbit.js @@ -0,0 +1,213 @@ +var bidfactory = require('../bidfactory.js'), + bidmanager = require('../bidmanager.js'), + utils = require('../utils.js'), + adloader = require('../adloader'); + +var WideOrbitAdapter = function WideOrbitAdapter() { + var pageImpression = 'JSAdservingMP.ashx?pc={pc}&pbId={pbId}&clk=&exm=&jsv=1.0&tsv=1.0&cts={cts}&arp=0&fl=0&vitp=&vit=&jscb=window.pbjs.handleWideOrbitCallback&url=&fp=&oid=&exr=&mraid=&apid=&apbndl=&mpp=0&uid=&cb={cb}&hb=1', + pageRepeatCommonParam = '&gid{o}={gid}&pp{o}=&clk{o}=&rpos{o}={rpos}&ecpm{o}={ecpm}&ntv{o}=&ntl{o}=&adsid{o}=', + pageRepeatParamId = '&pId{o}={pId}&rank{o}={rank}', + pageRepeatParamNamed = '&wsName{o}={wsName}&wName{o}={wName}&rank{o}={rank}&bfDim{o}={width}x{height}&subp{o}={subp}', + base = (window.location.protocol) + '//p{pbId}.atemda.com/', + bids, + adapterName = 'wideorbit'; + + function _fixParamNames(param) { + if (!param) { + return; + } + + var properties = ['site', 'page', 'width', 'height', 'rank', 'subPublisher', 'ecpm', 'atf', 'pId', 'pbId'], + prop; + + utils._each(properties, function (correctName) { + for (prop in param) { + if (param.hasOwnProperty(prop) && prop.toLowerCase() === correctName.toLowerCase()) { + param[correctName] = param[prop]; + break; + } + } + }); + } + + function _setParam(str, param, value) { + var pattern = new RegExp('{' + param + '}', 'g'); + + if (value === true) { + value = 1; + } + if (value === false) { + value = 0; + } + return str.replace(pattern, value); + } + + function _setParams(str, keyValuePairs) { + utils._each(keyValuePairs, function (keyValuePair) { + str = _setParam(str, keyValuePair[0], keyValuePair[1]); + }); + return str; + } + + function _setCommonParams(pos, params) { + return _setParams(pageRepeatCommonParam, [ + ['o', pos], + ['gid', encodeURIComponent(params.tagId)], + ['rpos', params.atf ? 1001 : 0], + ['ecpm', params.ecpm || ''] + ]); + } + + function _getRankParam(rank, pos) { + return rank || pos; + } + + function _setupIdPlacementParameters(pos, params) { + return _setParams(pageRepeatParamId, [ + ['o', pos], + ['pId', params.pId], + ['rank', _getRankParam(params.rank, pos)] + ]); + } + + function _setupNamedPlacementParameters(pos, params) { + return _setParams(pageRepeatParamNamed, [ + ['o', pos], + ['wsName', encodeURIComponent(decodeURIComponent(params.site))], + ['wName', encodeURIComponent(decodeURIComponent(params.page))], + ['width', params.width], + ['height', params.height], + ['subp', params.subPublisher ? encodeURIComponent(decodeURIComponent(params.subPublisher)) : ''], + ['rank', _getRankParam(params.rank, pos)] + ]); + } + + function _setupAdCall(publisherId, placementCount, placementsComponent) { + return _setParams(base + pageImpression, [ + ['pbId', publisherId], + ['pc', placementCount], + ['cts', new Date().getTime()], + ['cb', Math.floor(Math.random() * 100000000)] + ]) + placementsComponent; + } + + function _setupPlacementParameters(pos, params) { + var commonParams = _setCommonParams(pos, params); + + if (params.pId) { + return _setupIdPlacementParameters(pos, params) + commonParams; + } + + return _setupNamedPlacementParameters(pos, params) + commonParams; + } + + function _callBids(params) { + var publisherId, + bidUrl = '', + i; + + bids = params.bids || []; + + for (i = 0; i < bids.length; i++) { + var requestParams = bids[i].params; + + requestParams.tagId = bids[i].placementCode; + + _fixParamNames(requestParams); + + publisherId = requestParams.pbId; + bidUrl += _setupPlacementParameters(i, requestParams); + } + + bidUrl = _setupAdCall(publisherId, bids.length, bidUrl); + + utils.logMessage('Calling WO: ' + bidUrl); + + adloader.loadScript(bidUrl); + } + + function _processUserMatchings(userMatchings) { + var headElem = document.getElementsByTagName('head')[0], + createdElem; + + utils._each(userMatchings, function (userMatching) { + switch (userMatching.Type) { + case 'redirect': + createdElem = document.createElement('img'); + break; + case 'iframe': + createdElem = utils.createInvisibleIframe(); + break; + case 'javascript': + createdElem = document.createElement('script'); + createdElem.type = 'text/javascript'; + createdElem.async = true; + break; + } + createdElem.src = decodeURIComponent(userMatching.Url); + headElem.insertBefore(createdElem, headElem.firstChild); + }); + } + + function _getBidResponse(id, placements) { + var i; + + for (i = 0; i < placements.length; i++) { + if (placements[i].ExtPlacementId === id) { + return placements[i]; + } + } + } + + function _isUrl(scr) { + return scr.slice(0, 6) === "http:/" || scr.slice(0, 7) === "https:/" || scr.slice(0, 2) === "//"; + } + + function _buildAdCode(placement) { + var adCode = placement.Source, pixelTag; + + utils._each(placement.TrackingCodes, function (trackingCode) { + if (_isUrl(trackingCode)) { + pixelTag = ''; + } else { + pixelTag = trackingCode; + } + adCode = pixelTag + adCode; + }); + + return adCode; + } + + window.pbjs = window.pbjs || {}; + window.pbjs.handleWideOrbitCallback = function (response) { + var bidResponse, + bidObject; + + utils.logMessage('WO response. Placements: ' + response.Placements.length); + + _processUserMatchings(response.UserMatchings); + + utils._each(bids, function (bid) { + bidResponse = _getBidResponse(bid.placementCode, response.Placements); + + if (bidResponse && bidResponse.Type === 'DirectHTML') { + bidObject = bidfactory.createBid(1); + bidObject.cpm = bidResponse.Bid; + bidObject.ad = _buildAdCode(bidResponse); + bidObject.width = bidResponse.Width; + bidObject.height = bidResponse.Height; + } else { + bidObject = bidfactory.createBid(2); + } + + bidObject.bidderCode = adapterName; + bidmanager.addBidResponse(bid.placementCode, bidObject); + }); + }; + + return { + callBids: _callBids + }; +}; + +module.exports = WideOrbitAdapter; diff --git a/test/spec/adapters/wideorbit_spec.js b/test/spec/adapters/wideorbit_spec.js new file mode 100644 index 00000000000..d86e23fa006 --- /dev/null +++ b/test/spec/adapters/wideorbit_spec.js @@ -0,0 +1,491 @@ +describe('wideorbit adapter tests', function () { + + var expect = require('chai').expect; + var urlParse = require('url-parse'); + + // FYI: querystringify will perform encoding/decoding + var querystringify = require('querystringify'); + + var adapter = require('src/adapters/wideorbit'); + var adLoader = require('src/adloader'); + var bidmanager = require('src/bidmanager'); + + describe('creation of bid url', function () { + + var spyLoadScript; + + beforeEach(function () { + spyLoadScript = sinon.spy(adLoader, 'loadScript'); + }); + + afterEach(function () { + spyLoadScript.restore(); + }); + + it('should be called only once', function () { + + var params = { + bidderCode: 'wideorbit', + bids: [ + { + bidder: 'wideorbit', + params: { + pbId: 1, + pId: 101 + }, + placementCode: 'div-gpt-ad-12345-1' + }, + { + bidder: 'wideorbit', + params: { + pbId: 1, + site: 'Site 1', + page: 'Page 1', + width: 100, + height: 200, + subPublisher: 'Sub Publisher 1' + }, + placementCode: 'div-gpt-ad-12345-2' + } + ] + }; + + adapter().callBids(params); + + sinon.assert.calledOnce(spyLoadScript); + + }); + + it('should fix parameters name', function () { + + var params = { + bidderCode: 'wideorbit', + bids: [ + { + bidder: 'wideorbit', + params: { + PBiD: 1, + PID: 101 + }, + placementCode: 'div-gpt-ad-12345-1' + }, + { + bidder: 'wideorbit', + params: { + pbid: 1, + SiTe: 'Site 1', + Page: 'Page 1', + widTH: 100, + HEIGHT: 200, + SUBPublisher: 'Sub Publisher 1' + }, + placementCode: 'div-gpt-ad-12345-2' + } + ] + }; + + adapter().callBids(params); + + var bidUrl = spyLoadScript.getCall(0).args[0]; + + sinon.assert.calledWith(spyLoadScript, bidUrl); + + var parsedBidUrl = urlParse(bidUrl); + var parsedBidUrlQueryString = querystringify.parse(parsedBidUrl.query); + + expect(parsedBidUrl.hostname).to.equal('p1.atemda.com') + expect(parsedBidUrl.pathname).to.equal('/JSAdservingMP.ashx') + expect(parsedBidUrlQueryString).to.have.property('pc').and.to.equal('2'); + expect(parsedBidUrlQueryString).to.have.property('pbId').and.to.equal('1'); + expect(parsedBidUrlQueryString).to.have.property('jsv').and.to.equal('1.0'); + expect(parsedBidUrlQueryString).to.have.property('tsv').and.to.equal('1.0'); + expect(parsedBidUrlQueryString).to.have.property('cts').to.have.length.above(0); + expect(parsedBidUrlQueryString).to.have.property('arp').and.to.equal('0'); + expect(parsedBidUrlQueryString).to.have.property('fl').and.to.equal('0'); + expect(parsedBidUrlQueryString).to.have.property('jscb').and.to.equal('window.pbjs.handleWideOrbitCallback'); + expect(parsedBidUrlQueryString).to.have.property('mpp').and.to.equal('0'); + expect(parsedBidUrlQueryString).to.have.property('cb').to.have.length.above(0); + expect(parsedBidUrlQueryString).to.have.property('hb').and.to.equal('1'); + + expect(parsedBidUrlQueryString).to.have.property('gid0').and.to.equal('div-gpt-ad-12345-1'); + expect(parsedBidUrlQueryString).to.have.property('rpos0').and.to.equal('0'); + expect(parsedBidUrlQueryString).to.have.property('ecpm0').and.to.equal(''); + + expect(parsedBidUrlQueryString).to.have.property('gid1').and.to.equal('div-gpt-ad-12345-2'); + expect(parsedBidUrlQueryString).to.have.property('rpos1').and.to.equal('0'); + expect(parsedBidUrlQueryString).to.have.property('ecpm1').and.to.equal(''); + + expect(parsedBidUrlQueryString).to.have.property('pId0').and.to.equal('101'); + expect(parsedBidUrlQueryString).to.have.property('rank0').and.to.equal('0'); + + expect(parsedBidUrlQueryString).to.have.property('wsName1').and.to.equal('Site 1'); + expect(parsedBidUrlQueryString).to.have.property('wName1').and.to.equal('Page 1'); + expect(parsedBidUrlQueryString).to.have.property('rank1').and.to.equal('1'); + expect(parsedBidUrlQueryString).to.have.property('bfDim1').and.to.equal('100x200'); + expect(parsedBidUrlQueryString).to.have.property('subp1').and.to.equal('Sub Publisher 1'); + + }); + + describe('placement by name', function () { + + it('should be called with specific parameters for two bids', function () { + + var params = { + bidderCode: 'wideorbit', + bids: [ + { + bidder: 'wideorbit', + params: { + pbId: 1, + site: 'Site 1', + page: 'Page 1', + width: 100, + height: 200, + subPublisher: 'Sub Publisher 1', + atf: true + }, + placementCode: 'div-gpt-ad-12345-1' + }, + { + bidder: 'wideorbit', + params: { + pbId: 1, + site: 'Site 2', + page: 'Page 2', + width: 200, + height: 300, + rank: 123, + ecpm: 1.8 + }, + placementCode: 'div-gpt-ad-12345-2' + } + ] + }; + + adapter().callBids(params); + + var bidUrl = spyLoadScript.getCall(0).args[0]; + + sinon.assert.calledWith(spyLoadScript, bidUrl); + + var parsedBidUrl = urlParse(bidUrl); + var parsedBidUrlQueryString = querystringify.parse(parsedBidUrl.query); + + expect(parsedBidUrl.hostname).to.equal('p1.atemda.com') + expect(parsedBidUrl.pathname).to.equal('/JSAdservingMP.ashx') + expect(parsedBidUrlQueryString).to.have.property('pc').and.to.equal('2'); + expect(parsedBidUrlQueryString).to.have.property('pbId').and.to.equal('1'); + expect(parsedBidUrlQueryString).to.have.property('jsv').and.to.equal('1.0'); + expect(parsedBidUrlQueryString).to.have.property('tsv').and.to.equal('1.0'); + expect(parsedBidUrlQueryString).to.have.property('cts').to.have.length.above(0); + expect(parsedBidUrlQueryString).to.have.property('arp').and.to.equal('0'); + expect(parsedBidUrlQueryString).to.have.property('fl').and.to.equal('0'); + expect(parsedBidUrlQueryString).to.have.property('jscb').and.to.equal('window.pbjs.handleWideOrbitCallback'); + expect(parsedBidUrlQueryString).to.have.property('mpp').and.to.equal('0'); + expect(parsedBidUrlQueryString).to.have.property('cb').to.have.length.above(0); + expect(parsedBidUrlQueryString).to.have.property('hb').and.to.equal('1'); + + expect(parsedBidUrlQueryString).to.have.property('gid0').and.to.equal('div-gpt-ad-12345-1'); + expect(parsedBidUrlQueryString).to.have.property('rpos0').and.to.equal('1001'); + expect(parsedBidUrlQueryString).to.have.property('ecpm0').and.to.equal(''); + + expect(parsedBidUrlQueryString).to.have.property('gid1').and.to.equal('div-gpt-ad-12345-2'); + expect(parsedBidUrlQueryString).to.have.property('rpos1').and.to.equal('0'); + expect(parsedBidUrlQueryString).to.have.property('ecpm1').and.to.equal('1.8'); + + expect(parsedBidUrlQueryString).to.have.property('wsName0').and.to.equal('Site 1'); + expect(parsedBidUrlQueryString).to.have.property('wName0').and.to.equal('Page 1'); + expect(parsedBidUrlQueryString).to.have.property('rank0').and.to.equal('0'); + expect(parsedBidUrlQueryString).to.have.property('bfDim0').and.to.equal('100x200'); + expect(parsedBidUrlQueryString).to.have.property('subp0').and.to.equal('Sub Publisher 1'); + + expect(parsedBidUrlQueryString).to.have.property('wsName1').and.to.equal('Site 2'); + expect(parsedBidUrlQueryString).to.have.property('wName1').and.to.equal('Page 2'); + expect(parsedBidUrlQueryString).to.have.property('rank1').and.to.equal('123'); + expect(parsedBidUrlQueryString).to.have.property('bfDim1').and.to.equal('200x300'); + expect(parsedBidUrlQueryString).to.have.property('subp1').and.to.equal(''); + + }); + + }); + + describe('placement by id', function () { + + it('should be called with specific parameters for two bids', function () { + + var params = { + bidderCode: 'wideorbit', + bids: [ + { + bidder: 'wideorbit', + params: { + pbId: 1, + pId: 101, + atf: true, + ecpm: 0.8 + }, + placementCode: 'div-gpt-ad-12345-1' + }, + { + bidder: 'wideorbit', + params: { + pbId: 1, + pId: 102, + rank: 123 + }, + placementCode: 'div-gpt-ad-12345-2' + } + ] + }; + + adapter().callBids(params); + + var bidUrl = spyLoadScript.getCall(0).args[0]; + + sinon.assert.calledWith(spyLoadScript, bidUrl); + + var parsedBidUrl = urlParse(bidUrl); + var parsedBidUrlQueryString = querystringify.parse(parsedBidUrl.query); + + expect(parsedBidUrl.hostname).to.equal('p1.atemda.com') + expect(parsedBidUrl.pathname).to.equal('/JSAdservingMP.ashx') + expect(parsedBidUrlQueryString).to.have.property('pc').and.to.equal('2'); + expect(parsedBidUrlQueryString).to.have.property('pbId').and.to.equal('1'); + expect(parsedBidUrlQueryString).to.have.property('jsv').and.to.equal('1.0'); + expect(parsedBidUrlQueryString).to.have.property('tsv').and.to.equal('1.0'); + expect(parsedBidUrlQueryString).to.have.property('cts').to.have.length.above(0); + expect(parsedBidUrlQueryString).to.have.property('arp').and.to.equal('0'); + expect(parsedBidUrlQueryString).to.have.property('fl').and.to.equal('0'); + expect(parsedBidUrlQueryString).to.have.property('jscb').and.to.equal('window.pbjs.handleWideOrbitCallback'); + expect(parsedBidUrlQueryString).to.have.property('mpp').and.to.equal('0'); + expect(parsedBidUrlQueryString).to.have.property('cb').to.have.length.above(0); + expect(parsedBidUrlQueryString).to.have.property('hb').and.to.equal('1'); + + expect(parsedBidUrlQueryString).to.have.property('gid0').and.to.equal('div-gpt-ad-12345-1'); + expect(parsedBidUrlQueryString).to.have.property('rpos0').and.to.equal('1001'); + expect(parsedBidUrlQueryString).to.have.property('ecpm0').and.to.equal('0.8'); + + expect(parsedBidUrlQueryString).to.have.property('gid1').and.to.equal('div-gpt-ad-12345-2'); + expect(parsedBidUrlQueryString).to.have.property('rpos1').and.to.equal('0'); + expect(parsedBidUrlQueryString).to.have.property('ecpm1').and.to.equal(''); + + expect(parsedBidUrlQueryString).to.have.property('pId0').and.to.equal('101'); + expect(parsedBidUrlQueryString).to.have.property('rank0').and.to.equal('0'); + + expect(parsedBidUrlQueryString).to.have.property('pId1').and.to.equal('102'); + expect(parsedBidUrlQueryString).to.have.property('rank1').and.to.equal('123'); + + }); + + }); + + }); + + describe('handling of the callback response', function () { + + var placements = [ + { + ExtPlacementId: 'div-gpt-ad-12345-1', + Type: 'DirectHTML', + Bid: 1.3, + Width: 50, + Height: 100, + Source: '
    The AD 1 itself...
    ', + TrackingCodes: [ + 'https://www.admeta.com/1.gif' + ] + }, + { + ExtPlacementId: 'div-gpt-ad-12345-2', + Type: 'DirectHTML', + Bid: 1.5, + Width: 100, + Height: 200, + Source: '
    The AD 2 itself...
    ', + TrackingCodes: [ + 'http://www.admeta.com/2a.gif', + '' + ] + }, + { + ExtPlacementId: 'div-gpt-ad-12345-3', + Type: 'Other', + Bid: 1.7, + Width: 150, + Height: 250, + Source: '
    The AD 3 itself...
    ', + TrackingCodes: [ + 'http://www.admeta.com/3.gif' + ] + } + ]; + + it('callback function should exist', function () { + expect(pbjs.handleWideOrbitCallback).to.exist.and.to.be.a('function'); + }); + + it('bidmanager.addBidResponse should be called thrice with correct arguments', function () { + + var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); + + var params = { + bidderCode: 'wideorbit', + bids: [ + { + bidder: 'wideorbit', + params: { + pbId: 1, + pId: 101 + }, + placementCode: 'div-gpt-ad-12345-1' + }, + { + bidder: 'wideorbit', + params: { + pbId: 1, + site: 'Site 1', + page: 'Page 1', + width: 100, + height: 200, + subPublisher: 'Sub Publisher 1' + }, + placementCode: 'div-gpt-ad-12345-2' + }, + { + bidder: 'wideorbit', + params: { + pbId: 1, + pId: 102 + }, + placementCode: 'div-gpt-ad-12345-3' + }, + ] + }; + + var response = { + UserMatchings: [ + { + Type: 'redirect', + Url: 'http%3A%2F%2Fwww.admeta.com%2F1.gif' + } + ], + Placements: placements + }; + + adapter().callBids(params); + pbjs.handleWideOrbitCallback(response); + + var bidPlacementCode1 = stubAddBidResponse.getCall(0).args[0]; + var bidObject1 = stubAddBidResponse.getCall(0).args[1]; + var bidPlacementCode2 = stubAddBidResponse.getCall(1).args[0]; + var bidObject2 = stubAddBidResponse.getCall(1).args[1]; + var bidPlacementCode3 = stubAddBidResponse.getCall(2).args[0]; + var bidObject3 = stubAddBidResponse.getCall(2).args[1]; + + expect(bidPlacementCode1).to.equal('div-gpt-ad-12345-1'); + expect(bidObject1.cpm).to.equal(1.3); + expect(bidObject1.ad).to.equal('
    The AD 1 itself...
    '); + expect(bidObject1.width).to.equal(50); + expect(bidObject1.height).to.equal(100); + expect(bidObject1.getStatusCode()).to.equal(1); + expect(bidObject1.bidderCode).to.equal('wideorbit'); + + expect(bidPlacementCode2).to.equal('div-gpt-ad-12345-2'); + expect(bidObject2.cpm).to.equal(1.50); + expect(bidObject2.ad).to.equal('
    The AD 2 itself...
    '); + expect(bidObject2.width).to.equal(100); + expect(bidObject2.height).to.equal(200); + expect(bidObject2.getStatusCode()).to.equal(1); + expect(bidObject2.bidderCode).to.equal('wideorbit'); + + expect(bidPlacementCode3).to.equal('div-gpt-ad-12345-3'); + expect(bidObject3.getStatusCode()).to.equal(2); + expect(bidObject3.bidderCode).to.equal('wideorbit'); + + sinon.assert.calledWith(stubAddBidResponse, bidPlacementCode1, bidObject1); + sinon.assert.calledWith(stubAddBidResponse, bidPlacementCode2, bidObject2); + sinon.assert.calledWith(stubAddBidResponse, bidPlacementCode3, bidObject3); + + sinon.assert.calledThrice(stubAddBidResponse); + + stubAddBidResponse.restore(); + + }); + + it('should append an image to the head when type is set to redirect', function () { + + var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); + + var response = { + UserMatchings: [ + { + Type: 'redirect', + Url: 'http%3A%2F%2Fwww.admeta.com%2F1.gif' + } + ], + Placements: placements + }; + + pbjs.handleWideOrbitCallback(response); + + var imgElement = document.querySelectorAll("head img")[0]; + + expect(imgElement).to.exist; + expect(imgElement.src).to.equal('http://www.admeta.com/1.gif'); + + stubAddBidResponse.restore(); + }); + + it('should append an iframe to the head when type is set to iframe', function () { + + var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); + + var response = { + UserMatchings: [ + { + Type: 'iframe', + Url: 'http%3A%2F%2Fwww.admeta.com%2F1.ashx' + } + ], + Placements: placements + }; + + pbjs.handleWideOrbitCallback(response); + + var iframeElement = document.querySelectorAll("head iframe")[0]; + + expect(iframeElement).to.exist; + expect(iframeElement.src).to.equal('http://www.admeta.com/1.ashx'); + + stubAddBidResponse.restore(); + + }); + + it('should append an script to the head when type is set to javascript', function () { + + var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); + + var response = { + UserMatchings: [ + { + Type: 'javascript', + Url: 'http%3A%2F%2Fwww.admeta.com%2F1.js' + } + ], + Placements: placements + }; + + pbjs.handleWideOrbitCallback(response); + + var scriptElement = document.querySelectorAll("head script")[0]; + + expect(scriptElement).to.exist; + expect(scriptElement.src).to.equal('http://www.admeta.com/1.js'); + + stubAddBidResponse.restore(); + }); + + }); + +}); + \ No newline at end of file From ab3b23696b122fe917566be6fa64dfc7e3e95be1 Mon Sep 17 00:00:00 2001 From: Matt Lane Date: Tue, 7 Jun 2016 11:23:21 -0700 Subject: [PATCH 137/160] Pass Deal IDs on bids to ad server as targeting data (#390) If a bid response contains a dealId, its ad server targeting will receive a key with the form `hb_deal_`. Bids with the highest cpm still always win whether they contain a deal or not. If a winning bid does have a dealId, its ad server targeting will also have a key of `hb_deal` with the value of the deal id. Deals are always sent to the server, regardless of whether `enableSendAllBids` is on or off. * Construct required key/value pairs for bids with deals * Always attach bids with deals to targeting * Test addBidResponse places dealIds in adserver targeting --- src/bidmanager.js | 5 +++++ src/prebid.js | 26 +++++++++++++++++++++++++- test/spec/bidmanager_spec.js | 12 ++++++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) diff --git a/src/bidmanager.js b/src/bidmanager.js index e0f9adbe35e..dcc6627faeb 100644 --- a/src/bidmanager.js +++ b/src/bidmanager.js @@ -96,6 +96,11 @@ exports.addBidResponse = function (adUnitCode, bid) { var keyValues = {}; if (bid.bidderCode && bid.cpm !== 0) { keyValues = getKeyValueTargetingPairs(bid.bidderCode, bid); + + if (bid.dealId) { + keyValues[`hb_deal_${bid.bidderCode}`] = bid.dealId; + } + bid.adserverTargeting = keyValues; } diff --git a/src/prebid.js b/src/prebid.js index 311ead9e640..7f1e5b32acb 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -155,6 +155,11 @@ function getWinningBidTargeting() { timeToRespond: 0 })); + // winning bids with deals need an hb_deal targeting key + winners + .filter(bid => bid.dealId) + .map(bid => bid.adserverTargeting.hb_deal = bid.dealId); + winners = winners.map(winner => { return { [winner.adUnitCode]: Object.keys(winner.adserverTargeting, key => key) @@ -171,6 +176,22 @@ function getWinningBidTargeting() { return winners; } +function getDealTargeting() { + const dealTargeting = pbjs._bidsReceived.filter(bid => bid.dealId).map(bid => { + const dealKey = `hb_deal_${bid.bidderCode}`; + return { + [bid.adUnitCode]: CONSTANTS.TARGETING_KEYS.map(key => { + return { + [`${key}_${bid.bidderCode}`.substring(0, 20)]: [bid.adserverTargeting[key]] + }; + }) + .concat({ [dealKey]: [bid.adserverTargeting[dealKey]] }) + }; + }); + + return dealTargeting; +} + function getBidLandscapeTargeting() { const standardKeys = CONSTANTS.TARGETING_KEYS; @@ -188,7 +209,10 @@ function getBidLandscapeTargeting() { } function getAllTargeting() { - return getWinningBidTargeting().concat(pb_sendAllBids ? getBidLandscapeTargeting() : []); + let targeting = getWinningBidTargeting(); + // deals are always attached to targeting + targeting = getDealTargeting().concat(targeting); + return targeting.concat(pb_sendAllBids ? getBidLandscapeTargeting() : []); } ////////////////////////////////// diff --git a/test/spec/bidmanager_spec.js b/test/spec/bidmanager_spec.js index b5166ab6917..17498bd67e1 100644 --- a/test/spec/bidmanager_spec.js +++ b/test/spec/bidmanager_spec.js @@ -390,5 +390,17 @@ describe('bidmanager.js', function () { registeredBid = pbjs._bidsReceived.pop(); assert.equal(registeredBid.pbDg, expectedIncrement, '20+ caps at 20.00'); }); + + it('should place dealIds in adserver targeting', () => { + const bid = Object.assign({}, + bidfactory.createBid(2), + fixtures.getBidResponses()[0] + ); + + bid.dealId = "test deal"; + bidmanager.addBidResponse(bid.adUnitCode, bid); + const addedBid = pbjs._bidsReceived.pop(); + assert.equal(addedBid.adserverTargeting[`hb_deal_${bid.bidderCode}`], bid.dealId, 'dealId placed in adserverTargeting'); + }); }); }); From 0aca2ebd4ac2c708b40090af80df3d14b7340b67 Mon Sep 17 00:00:00 2001 From: sekindo Date: Wed, 15 Jun 2016 00:47:55 +0300 Subject: [PATCH 138/160] Sekindo Prebid Adapter : (#355) * Sekindo Prebid Adaptor : * add placement id to error log * append iframe to head * refactor * use triple equality & adding sekindo to package.json * logging * frameDoc.open() --- integrationExamples/gpt/pbjs_example_gpt.html | 9 +- package.json | 1 + src/adapters/sekindo.js | 114 ++++++++++++++++++ 3 files changed, 123 insertions(+), 1 deletion(-) create mode 100755 src/adapters/sekindo.js diff --git a/integrationExamples/gpt/pbjs_example_gpt.html b/integrationExamples/gpt/pbjs_example_gpt.html index 59631ba9c84..e930f61b3a6 100644 --- a/integrationExamples/gpt/pbjs_example_gpt.html +++ b/integrationExamples/gpt/pbjs_example_gpt.html @@ -173,7 +173,14 @@ publisher_id: 5000563, // REQUIRED int or str publisher ID. To get one, register at https://control.adequant.com bidfloor: 0.01, // OPTIONAL float bid floor in $ CPM } - } + }, + { + bidder: 'sekindo', + params: { + spaceId: 14071, // REQUIRED int. To get one, contact http://www.sekindo.com + bidfloor: 0.2 // OPTIONAL float bid floor in $ CPM + } + } ] }, { code: 'div-gpt-ad-12345678-1', diff --git a/package.json b/package.json index d3f1da1a039..d360e016d87 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "pubmatic", "pulsepoint", "rubicon", + "sekindo", "sonobi", "sovrn", "springserve", diff --git a/src/adapters/sekindo.js b/src/adapters/sekindo.js new file mode 100755 index 00000000000..8c25f534cb3 --- /dev/null +++ b/src/adapters/sekindo.js @@ -0,0 +1,114 @@ +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 SekindoAdapter; +SekindoAdapter = function SekindoAdapter() { + + 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; + } + } + + pbjs.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.adId = response.adId; + 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('sekindo callback general error'); + } + } + }; + + function _requestBids(bid, callbackId, pubUrl) + { + //determine tag params + var spaceId = utils.getBidIdParamater('spaceId', bid.params); + var bidfloor = utils.getBidIdParamater('bidfloor', bid.params); + var protocol = ('https:' === document.location.protocol ? 's' : ''); + var scriptSrc = 'https://live.sekindo.com/live/liveView.php?'; + + scriptSrc = utils.tryAppendQueryString(scriptSrc, 's', spaceId); + scriptSrc = utils.tryAppendQueryString(scriptSrc, 'pubUrl', pubUrl); + scriptSrc = utils.tryAppendQueryString(scriptSrc, 'hbcb', callbackId); + scriptSrc = utils.tryAppendQueryString(scriptSrc, 'dcpmflr', bidfloor); + scriptSrc = utils.tryAppendQueryString(scriptSrc, 'hbto', pbjs.bidderTimeout); + scriptSrc = utils.tryAppendQueryString(scriptSrc, 'protocol', protocol); + + var html = ''; + + var iframe = utils.createInvisibleIframe(); + iframe.id = 'skIfr_'+callbackId; + + var elToAppend = document.getElementsByTagName('head')[0]; + //insert the iframe into document + elToAppend.insertBefore(iframe, elToAppend.firstChild); + + var iframeDoc = utils.getIframeDocument(iframe); + iframeDoc.open(); + iframeDoc.write(html); + iframeDoc.close(); + } + + return { + callBids: _callBids + }; +}; + + +module.exports = SekindoAdapter; + From 11efdd523b5bd5ba8e04e74daf32b1e4830931d3 Mon Sep 17 00:00:00 2001 From: Seth Yates Date: Wed, 15 Jun 2016 15:17:36 +1000 Subject: [PATCH 139/160] feat(krux): add Krux Link adapter (#384) --- integrationExamples/gpt/pbjs_example_gpt.html | 50 ++++++------ package.json | 1 + src/adapters/kruxlink.js | 79 +++++++++++++++++++ 3 files changed, 106 insertions(+), 24 deletions(-) create mode 100644 src/adapters/kruxlink.js diff --git a/integrationExamples/gpt/pbjs_example_gpt.html b/integrationExamples/gpt/pbjs_example_gpt.html index e930f61b3a6..477f5319a1b 100644 --- a/integrationExamples/gpt/pbjs_example_gpt.html +++ b/integrationExamples/gpt/pbjs_example_gpt.html @@ -1,9 +1,8 @@ + - - +Prebid.js integration example - - - -

    Prebid.js Test

    @@ -405,7 +406,7 @@

    Prebid.js Test

    -
    +
    + + diff --git a/package.json b/package.json index d360e016d87..19395e42a25 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "aol", "appnexus", "indexExchange", + "kruxlink", "openx", "pubmatic", "pulsepoint", diff --git a/src/adapters/kruxlink.js b/src/adapters/kruxlink.js new file mode 100644 index 00000000000..547f502478f --- /dev/null +++ b/src/adapters/kruxlink.js @@ -0,0 +1,79 @@ +var bidfactory = require('../bidfactory.js'); +var bidmanager = require('../bidmanager.js'); +var adloader = require('../adloader.js'); + +function _qs(key, value) { + return encodeURIComponent(key) + '=' + encodeURIComponent(value); +} + +function _makeBidResponse(placementCode, bid) { + var bidResponse = bidfactory.createBid(bid !== undefined ? 1 : 2); + bidResponse.bidderCode = 'kruxlink'; + if (bid !== undefined) { + bidResponse.cpm = bid.price; + bidResponse.ad = bid.adm; + bidResponse.width = bid.w; + bidResponse.height = bid.h; + } + bidmanager.addBidResponse(placementCode, bidResponse); +} + +function _makeCallback(id, placements) { + var callback = '_kruxlink_' + id; + pbjs[callback] = function(response) { + // Clean up our callback + delete pbjs[callback]; + + // Add in the bid respones + for (var i = 0; i < response.seatbid.length; i++) { + var seatbid = response.seatbid[i]; + for (var j = 0; j < seatbid.bid.length; j++) { + var bid = seatbid.bid[j]; + _makeBidResponse(placements[bid.impid], bid); + delete placements[bid.impid]; + } + } + + // Add any no-bids remaining + for (var placementCode in placements) { + if (placements.hasOwnProperty(placementCode)) { + _makeBidResponse(placementCode); + } + } + }; + + return 'pbjs.' + callback; +} + +function _callBids(params) { + var impids = []; + var placements = {}; + + var bids = params.bids || []; + for (var i = 0; i < bids.length; i++) { + var bidRequest = bids[i]; + var bidRequestParams = bidRequest.params || {}; + var impid = bidRequestParams.impid; + placements[impid] = bidRequest.placementCode; + + impids.push(impid); + } + + var callback = _makeCallback(params.bidderRequestId, placements); + var qs = [ + _qs('id', params.bidderRequestId), + _qs('u', window.location.href), + _qs('impid', impids.join(',')), + _qs('calltype', 'pbd'), + _qs('callback', callback) + ]; + var url = 'https://link.krxd.net/hb?' + qs.join('&'); + + adloader.loadScript(url); +} + +module.exports = function KruxAdapter() { + return { + callBids: _callBids + }; +}; From ed066aebef9499325786693b2e2977054e84e2dc Mon Sep 17 00:00:00 2001 From: aurelienjoneau Date: Wed, 15 Jun 2016 16:11:49 +0200 Subject: [PATCH 140/160] 2 sizes added to Rubicon adapter (#402) --- src/adapters/rubicon.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/adapters/rubicon.js b/src/adapters/rubicon.js index 17968e0c597..879aeaa2f99 100644 --- a/src/adapters/rubicon.js +++ b/src/adapters/rubicon.js @@ -33,7 +33,9 @@ var RubiconAdapter = function RubiconAdapter() { '320x480': 67, '1800x1000': 68, '480x320':101, - '768x1024': 102 + '768x1024': 102, + '1000x300':113, + '320x100':117 }; var RUBICON_INITIALIZED = 0; From 024d2b6c63f4cd336d7b7ae0db84c3892a816772 Mon Sep 17 00:00:00 2001 From: mkendall07 Date: Wed, 15 Jun 2016 10:58:49 -0400 Subject: [PATCH 141/160] Code style for Sekindo and update JSCS rule --- .jscsrc | 3 ++- src/adapters/sekindo.js | 27 +++++++++------------------ 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/.jscsrc b/.jscsrc index 2b70851dd97..a74740263ff 100644 --- a/.jscsrc +++ b/.jscsrc @@ -6,5 +6,6 @@ "validateIndentation": 2, "disallowSpacesInFunctionDeclaration": { "beforeOpeningRoundBrace": true - } + }, + "disallowNewlineBeforeBlockStatements": true } diff --git a/src/adapters/sekindo.js b/src/adapters/sekindo.js index 8c25f534cb3..967a1a703ef 100755 --- a/src/adapters/sekindo.js +++ b/src/adapters/sekindo.js @@ -26,19 +26,15 @@ SekindoAdapter = function SekindoAdapter() { } } - pbjs.sekindoCB = function(callbackId, response) - { + pbjs.sekindoCB = function(callbackId, response) { var bidObj = getBidRequest(callbackId); - if (typeof (response) !== 'undefined' && typeof (response.cpm) !== 'undefined') - { + if (typeof (response) !== 'undefined' && typeof (response.cpm) !== 'undefined') { var bid = []; - if (bidObj) - { + if (bidObj) { var bidCode = bidObj.bidder; var placementCode = bidObj.placementCode; - if (response.cpm !== undefined && response.cpm > 0) - { + if (response.cpm !== undefined && response.cpm > 0) { bid = bidfactory.createBid(CONSTANTS.STATUS.GOOD); bid.adId = response.adId; @@ -52,8 +48,7 @@ SekindoAdapter = function SekindoAdapter() { bidmanager.addBidResponse(placementCode, bid); } - else - { + else { bid = bidfactory.createBid(CONSTANTS.STATUS.NO_BID); bid.callback_uid = callbackId; bid.bidderCode = bidCode; @@ -61,21 +56,17 @@ SekindoAdapter = function SekindoAdapter() { } } } - else - { - if (bidObj) - { + else { + if (bidObj) { utils.logMessage('No prebid response for placement '+bidObj.placementCode); } - else - { + else { utils.logMessage('sekindo callback general error'); } } }; - function _requestBids(bid, callbackId, pubUrl) - { + function _requestBids(bid, callbackId, pubUrl) { //determine tag params var spaceId = utils.getBidIdParamater('spaceId', bid.params); var bidfloor = utils.getBidIdParamater('bidfloor', bid.params); From d1ddc4f063ce7b13debe78e14e2886bdec879f93 Mon Sep 17 00:00:00 2001 From: astudnicky Date: Fri, 29 Apr 2016 16:12:23 -0400 Subject: [PATCH 142/160] Update Sonobi adapter to accommodate single-size arrays Output errors for adops integrators Do not cancel bidding without sizes --- src/adapters/sonobi.js | 55 +++++++++++++++++++++++++++--------------- 1 file changed, 35 insertions(+), 20 deletions(-) diff --git a/src/adapters/sonobi.js b/src/adapters/sonobi.js index 6dc3d9fb37f..612f872711f 100644 --- a/src/adapters/sonobi.js +++ b/src/adapters/sonobi.js @@ -4,7 +4,7 @@ var adloader = require('../adloader.js'); var utils = require('../utils'); var SonobiAdapter = function SonobiAdapter(){ - var test = false; // tag tester = true || false + var test = false; // tag tester = true || false var cb_map = {}; function _phone_in(params){ @@ -13,36 +13,51 @@ var SonobiAdapter = function SonobiAdapter(){ adloader.loadScript(trinity + JSON.stringify(_keymaker(bids)) + '&cv=' + _operator(), null); } - function _keymaker(bids){ // Make keys + function _keymaker(bids){ // Make keys var keyring = {}; utils._each(bids, function(o){ - var sizes = []; - utils._each(o.sizes, function(size){ - sizes.push(size.join('x')); - }); - sizes = sizes.toString(); - if (!!o.params.ad_unit && o.params.ad_unit.length > 0) { - // Cypher - keyring[o.params.ad_unit + '|' + o.params.dom_id] = sizes; - cb_map[o.params.ad_unit + '|' + o.params.dom_id] = o.placementCode; - } else if (!!o.params.placement_id && o.params.placement_id.length > 0) { - // Morpheus - keyring[o.params.dom_id] = o.params.placement_id + (test ? '-test' : '') + '|' + sizes; - cb_map[o.params.dom_id] = o.placementCode; - } else { - utils.logError('sonobi unable to bid: Improper parameters for ' + o.placementCode); + var sizes = utils.parseSizesInput(o.sizes).toString(); + if (utils.isEmpty(sizes)){ + utils.logWarn('Sonobi adapter expects sizes for ' + o.placementCode); + } + switch(true){ + case (!o.params.ad_unit && !o.params.placement_id): + utils.logError('Sonobi unable to bid: Missing parameters for ' + o.placementCode); + break; + case (!!o.params.ad_unit && !!o.params.placement_id): + utils.logError('Sonobi unable to bid: Extra parameters for ' + o.placementCode); + break; + case (!!o.params.ad_unit && o.params.ad_unit.length === 0): + utils.logError('Sonobi unable to bid: Empty ad_unit for ' + o.placementCode); + break; + case (!!o.params.placement_id && o.params.placement_id.length === 0): + utils.logError('Sonobi unable to bid: Empty placement_id for ' + o.placementCode); + break; + case (!!o.params.placement_id): // Morpeus style + keyring[o.params.dom_id] = o.params.placement_id + (test ? '-test' : '') + '|' + sizes; + cb_map[o.params.dom_id] = o.placementCode; + break; + case (!!o.params.ad_unit && o.params.ad_unit.charAt(0) !== '/'): + // DFP docs do not necessarily require leading slash? - add it in if it's not there. + o.params.ad_unit = '/' + o.params.ad_unit; + case (!!o.params.ad_unit): // Cypher style + keyring[o.params.ad_unit + '|' + o.params.dom_id] = sizes; + cb_map[o.params.ad_unit + '|' + o.params.dom_id] = o.placementCode; + break; + default: // I don't know how it's broken, but it is. + utils.logError('Sonobi unable to bid: Improper parameters for ' + o.placementCode); } }); return keyring; } - function _operator(){ // Uniqify callbacks + function _operator(){ // Uniqify callbacks var uniq = "cb" + utils.getUniqueIdentifierStr(); window[uniq] = _trinity; return uniq; } - function _trinity(response){ // Callback + function _trinity(response){ // Callback var slots = response.slots || {}; var sbi_dc = response.sbi_dc || ''; var bidObject = {}; @@ -55,7 +70,7 @@ var SonobiAdapter = function SonobiAdapter(){ bidObject.width = Number(slots[slot].sbi_size.split('x')[0]); bidObject.height = Number(slots[slot].sbi_size.split('x')[1]); bidmanager.addBidResponse(cb_map[slot], bidObject); - } else { // No aid? No ad. + } else { // No aid? No ad. bidObject = bidfactory.createBid(2); bidObject.bidderCode = 'sonobi'; bidmanager.addBidResponse(cb_map[slot], bidObject); From 0439cc815a3f1119b15514ee6670c997ef8e317c Mon Sep 17 00:00:00 2001 From: Nate Guisinger Date: Fri, 27 May 2016 08:47:19 -0700 Subject: [PATCH 143/160] use bid request bidId for sovrn imp.id (#379) * use bid request bidId for sovrn imp.id * fixed placement undefined --- src/adapters/sovrn.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/adapters/sovrn.js b/src/adapters/sovrn.js index be3b90bcf4a..b1e38615f74 100644 --- a/src/adapters/sovrn.js +++ b/src/adapters/sovrn.js @@ -46,7 +46,7 @@ var SovrnAdapter = function SovrnAdapter() { var imp = { - id: utils.getUniqueIdentifierStr(), + id: bid.bidId, banner: { w: adW, h: adH @@ -105,7 +105,9 @@ var SovrnAdapter = function SovrnAdapter() { var bid = {}; // try to fetch the bid request we sent Sovrn - var bidObj = pbjs._bidsRequested.map(bidSet => bidSet.bids.filter(bid => bid.params && bid.params.impId === id)); + var bidObj = pbjs._bidsRequested.find(bidSet => bidSet.bidderCode === 'sovrn').bids + .find(bid => bid.bidId === id); + if (bidObj) { placementCode = bidObj.placementCode; placementsWithBidsBack.push(placementCode); From 899a643b9cb256503d3861daa6157bcb767bacbd Mon Sep 17 00:00:00 2001 From: Nate Guisinger Date: Tue, 31 May 2016 05:24:13 -0700 Subject: [PATCH 144/160] use refactored data structures (#382) --- src/adapters/yieldbot.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/adapters/yieldbot.js b/src/adapters/yieldbot.js index 47f241acfaa..88c290d6ddb 100644 --- a/src/adapters/yieldbot.js +++ b/src/adapters/yieldbot.js @@ -96,9 +96,7 @@ var YieldbotAdapter = function YieldbotAdapter() { yieldbot.pub(psn); yieldbot.defineSlot(slot, { sizes: bid.sizes || [] }); - var cbId = utils.getUniqueIdentifierStr(); - bidmanager.pbCallbackMap[cbId] = bid; - ybotlib.definedSlots.push(cbId); + ybotlib.definedSlots.push(bid.bidId); }); yieldbot.enableAsync(); @@ -126,7 +124,9 @@ var YieldbotAdapter = function YieldbotAdapter() { var placementCode; var adapterConfig; - adapterConfig = bidmanager.getPlacementIdByCBIdentifer(v) || {}; + adapterConfig = pbjs._bidsRequested + .find(bidderRequest => bidderRequest.bidderCode === 'yieldbot').bids + .find(bid => bid.bidId === v) || {}; slot = adapterConfig.params.slot || ''; criteria = yieldbot.getSlotCriteria(slot); From 917063cae4a0eb9a626b9fcc1fa71340e951ce82 Mon Sep 17 00:00:00 2001 From: Nate Guisinger Date: Tue, 31 May 2016 05:31:00 -0700 Subject: [PATCH 145/160] Restore return format of bidsBackHandler callback (#371) * a `groupByPlacement` reduce function transforms the bids received array to a map * prevent concurrent bid requests If an auction is running don't accept a new request for bids.. This adds a `clearAuction` function to set `auctionRunning` false and to clear `_bidsRequested` and `_bidsReceived`. This is a stopgap measure util #353 * clear data structures on additional bid request * make auctionRunning private and allow passing adUnits to requestBids * pass adUnitCodes on refresh bids * use adUnitCodes if passed in to requestBids, otherwise `getBidderCodes()`, disambiguate `bidmanager.getBidderCode` * restore use of adUnitCodes to filter adUnits * cherrypick reset targeting merged * clear targeting fixes and test mock * Add GptPubadsDefined check --- .gitignore | 1 + src/adaptermanager.js | 10 ++-- src/bidmanager.js | 33 ++++++++++-- src/prebid.js | 92 +++++++++++++++++++++++---------- test/spec/unit/pbjs_api_spec.js | 14 +++++ 5 files changed, 115 insertions(+), 35 deletions(-) diff --git a/.gitignore b/.gitignore index 1e4d5248000..c0a1a2f8ba4 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ build # Test Files test/app gpt.html +gpt-each-bidder3.html # Dev File diff --git a/src/adaptermanager.js b/src/adaptermanager.js index 1b9e4d12758..0243ec11c00 100644 --- a/src/adaptermanager.js +++ b/src/adaptermanager.js @@ -10,8 +10,8 @@ import { BaseAdapter } from './adapters/baseAdapter'; var _bidderRegistry = {}; exports.bidderRegistry = _bidderRegistry; -function getBids({ bidderCode, requestId, bidderRequestId }) { - return pbjs.adUnits.map(adUnit => { +function getBids({ bidderCode, requestId, bidderRequestId, adUnits }) { + return adUnits.map(adUnit => { return adUnit.bids.filter(bid => bid.bidder === bidderCode) .map(bid => Object.assign(bid, { placementCode: adUnit.code, @@ -23,10 +23,10 @@ function getBids({ bidderCode, requestId, bidderRequestId }) { }).reduce(flatten, []); } -exports.callBids = () => { +exports.callBids = ({ adUnits }) => { const requestId = utils.getUniqueIdentifierStr(); - getBidderCodes().forEach(bidderCode => { + getBidderCodes(adUnits).forEach(bidderCode => { const adapter = _bidderRegistry[bidderCode]; if (adapter) { const bidderRequestId = utils.getUniqueIdentifierStr(); @@ -34,7 +34,7 @@ exports.callBids = () => { bidderCode, requestId, bidderRequestId, - bids: getBids({ bidderCode, requestId, bidderRequestId }), + bids: getBids({ bidderCode, requestId, bidderRequestId, adUnits }), start: new Date().getTime() }; utils.logMessage(`CALLING BIDDER ======= ${bidderCode}`); diff --git a/src/bidmanager.js b/src/bidmanager.js index 7e053b83f60..e0f9adbe35e 100644 --- a/src/bidmanager.js +++ b/src/bidmanager.js @@ -22,7 +22,7 @@ const _hgPriceCap = 20.00; */ exports.getTimedOutBidders = function () { return pbjs._bidsRequested - .map(getBidderCodes) + .map(getBidderCode) .filter(uniques) .filter(bidder => pbjs._bidsReceived .map(getBidders) @@ -32,7 +32,7 @@ exports.getTimedOutBidders = function () { function timestamp() { return new Date().getTime(); } -function getBidderCodes(bidSet) { +function getBidderCode(bidSet) { return bidSet.bidderCode; } @@ -236,6 +236,8 @@ exports.executeCallback = function () { processCallbacks([externalOneTimeCallback]); externalOneTimeCallback = null; } + + pbjs.clearAuction(); }; function triggerAdUnitCallbacks(adUnitCode) { @@ -249,11 +251,36 @@ function processCallbacks(callbackQueue) { if (utils.isArray(callbackQueue)) { for (i = 0; i < callbackQueue.length; i++) { var func = callbackQueue[i]; - func.call(pbjs, pbjs._bidsReceived); + func.call(pbjs, pbjs._bidsReceived.reduce(groupByPlacement, {})); } } } +/** + * groupByPlacement is a reduce function that converts an array of Bid objects + * to an object with placement codes as keys, with each key representing an object + * with an array of `Bid` objects for that placement + * @param prev previous value as accumulator object + * @param item current array item + * @param idx current index + * @param arr the array being reduced + * @returns {*} as { [adUnitCode]: { bids: [Bid, Bid, Bid] } } + */ +function groupByPlacement(prev, item, idx, arr) { + // this uses a standard "array to map" operation that could be abstracted further + if (item.adUnitCode in Object.keys(prev)) { + // if the adUnitCode key is present in the accumulator object, continue + return prev; + } else { + // otherwise add the adUnitCode key to the accumulator object and set to an object with an + // array of Bids for that adUnitCode + prev[item.adUnitCode] = { + bids: arr.filter(bid => bid.adUnitCode === item.adUnitCode) + }; + return prev; + } +} + /** * Add a one time callback, that is discarded after it is called * @param {Function} callback [description] diff --git a/src/prebid.js b/src/prebid.js index 60bd3804c99..311ead9e640 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -26,6 +26,8 @@ var BID_TIMEOUT = CONSTANTS.EVENTS.BID_TIMEOUT; var pb_bidsTimedOut = false; var pb_sendAllBids = false; +var auctionRunning = false; +var presetTargeting = []; var eventValidators = { bidWon: checkDefinedPlacement @@ -35,6 +37,7 @@ var eventValidators = { pbjs._bidsRequested = []; pbjs._bidsReceived = []; +pbjs._adsReceived = []; //default timeout for all bids pbjs.bidderTimeout = pbjs.bidderTimeout || 2000; @@ -101,11 +104,35 @@ function checkDefinedPlacement(id) { return true; } +function resetPresetTargeting() { + if (isGptPubadsDefined()) { + window.googletag.pubads().getSlots().forEach(slot => { + slot.clearTargeting(); + }); + + setTargeting(presetTargeting); + } +} + +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 getWinningBidTargeting() { - let presets; if (isGptPubadsDefined()) { - presets = (function getPresetTargeting() { + presetTargeting = (function getPresetTargeting() { return window.googletag.pubads().getSlots().map(slot => { return { [slot.getAdUnitPath()]: slot.getTargetingKeys().map(key => { @@ -125,7 +152,7 @@ function getWinningBidTargeting() { adUnitCode: adUnitCode, cpm: 0, adserverTargeting: {}, - timeToRespond : 0 + timeToRespond: 0 })); winners = winners.map(winner => { @@ -137,8 +164,8 @@ function getWinningBidTargeting() { }; }); - if (presets) { - winners.concat(presets); + if (presetTargeting) { + winners.concat(presetTargeting); } return winners; @@ -189,12 +216,10 @@ pbjs.getAdserverTargetingForAdUnitCodeStr = function (adunitCode) { }; /** - * This function returns the query string targeting parameters available at this moment for a given ad unit. Note that some bidder's response may not have been received if you call this function too quickly after the requests are sent. - * @param {string} [adunitCode] adUnitCode to get the bid responses for - * @alias module:pbjs.getAdserverTargetingForAdUnitCode - * @return {object} returnObj return bids +* This function returns the query string targeting parameters available at this moment for a given ad unit. Note that some bidder's response may not have been received if you call this function too quickly after the requests are sent. + * @param adUnitCode {string} adUnitCode to get the bid responses for + * @returns {object} returnObj return bids */ - pbjs.getAdserverTargetingForAdUnitCode = function (adUnitCode) { utils.logInfo('Invoking pbjs.getAdserverTargetingForAdUnitCode', arguments); @@ -287,20 +312,7 @@ pbjs.setTargetingForGPTAsync = function () { return; } - window.googletag.pubads().getSlots().forEach(slot => { - getAllTargeting() - .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, index, array) => { - 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)); - })); - }); + setTargeting(getAllTargeting()); }; /** @@ -387,21 +399,47 @@ pbjs.removeAdUnit = function (adUnitCode) { } }; +pbjs.clearAuction = function() { + auctionRunning = false; + utils.logMessage('Prebid auction cleared'); +}; + /** * * @param bidsBackHandler * @param timeout + * @param adUnits + * @param adUnitCodes */ -pbjs.requestBids = function ({ bidsBackHandler, timeout }) { +pbjs.requestBids = function ({ bidsBackHandler, timeout, adUnits, adUnitCodes }) { + if (auctionRunning) { + utils.logError('Prebid Error: `pbjs.requestBids` was called while a previous auction was' + + ' still running. Resubmit this request.'); + return; + } else { + auctionRunning = true; + pbjs._bidsRequested = []; + pbjs._bidsReceived = []; + resetPresetTargeting(); + } + const cbTimeout = timeout || pbjs.bidderTimeout; + // use adUnits provided or from pbjs global + adUnits = adUnits || pbjs.adUnits; + + // if specific adUnitCodes filter adUnits for those codes + if (adUnitCodes && adUnitCodes.length) { + adUnits = adUnits.filter(adUnit => adUnitCodes.includes(adUnit.code)); + } + if (typeof bidsBackHandler === objectType_function) { bidmanager.addOneTimeCallback(bidsBackHandler); } utils.logInfo('Invoking pbjs.requestBids', arguments); - if (!pbjs.adUnits || pbjs.adUnits.length === 0) { + if (!adUnits || adUnits.length === 0) { utils.logMessage('No adUnits configured. No bids requested.'); return; } @@ -409,7 +447,7 @@ pbjs.requestBids = function ({ bidsBackHandler, timeout }) { //set timeout for all bids setTimeout(bidmanager.executeCallback, cbTimeout); - adaptermanager.callBids(); + adaptermanager.callBids({ adUnits, adUnitCodes }); }; /** diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index 83ca498731d..68ecc092200 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -19,6 +19,12 @@ pbjs = pbjs || {}; pbjs._bidsRequested = getBidRequests(); pbjs._bidsReceived = getBidResponses(); +function resetAuction() { + pbjs.clearAuction(); + pbjs._bidsRequested = getBidRequests(); + pbjs._bidsReceived = getBidResponses(); +} + var Slot = function Slot(elementId, pathId) { var slot = { getSlotElementId: function getSlotElementId() { @@ -38,6 +44,10 @@ var Slot = function Slot(elementId, pathId) { getTargetingKeys: function getTargetingKeys() { return ['testKey']; + }, + + clearTargeting: function clearTargeting() { + return googletag.pubads().getSlots(); } }; slot.spySetTargeting = sinon.spy(slot, 'setTargeting'); @@ -282,6 +292,7 @@ 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', () => { @@ -294,6 +305,7 @@ describe('Unit: Prebid Module', function () { assert.ok(logMessageSpy.calledWith('No adUnits configured. No bids requested.'), 'expected message was logged'); utils.logMessage.restore(); pbjs.adUnits = adUnitsBackup; + resetAuction(); }); it('should execute callback after timeout', () => { @@ -314,6 +326,7 @@ describe('Unit: Prebid Module', function () { bidmanager.executeCallback.restore(); clock.restore(); + resetAuction(); }); it('should call callBids function on adaptermanager', () => { @@ -321,6 +334,7 @@ describe('Unit: Prebid Module', function () { pbjs.requestBids({}); assert.ok(spyCallBids.called, 'called adaptermanager.callBids'); adaptermanager.callBids.restore(); + resetAuction(); }); }); From 22bf5b0316ebfbbbc79c260bb5ccf74c61422d2f Mon Sep 17 00:00:00 2001 From: protonate Date: Tue, 31 May 2016 13:42:19 -0700 Subject: [PATCH 146/160] Prebid v0.9.2 Release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c435caacc0c..2df9883ffe2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "0.9.1", + "version": "0.9.2", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From f9edc5ccb33aadda413a2deaa7a8a8d74650e7d8 Mon Sep 17 00:00:00 2001 From: bjorn-wo Date: Fri, 3 Jun 2016 17:49:09 +0200 Subject: [PATCH 147/160] WideOrbit adapter (#332) * Added WideOrbit adapter * Fix User Matching code * Renamed adapter + some minor JS issues * Added flag for notifying front action is header bidding. Use returned bid. * Duplicated properties fixed. * Correcting adaptermanager after wrong merge. Now all tests passes correctly. * Seting fl and jscb directly in the pageImpression-part of the url, since they are hardcoded. * Fixed above the fold parameter * No need to modify original value. * Normalizing file regarding single or double quotes. Also, properly indenting. * Removed uneeded properties: referrer (twice) and tagId. * Slightly better readiblity for variable declarations (and definition in some cases). * Refactoring regarding function naming and the way we set rank parameter. Also making sure we encode/decode site and page parameters. * Ignoring TypeScript's typing definitions. * Added initial WideOrbit Adapter tests covering url-creation part. * Completed wide orbit adapter tests with the one regarding callback response. * Slightly better test names. * Fix support for already prepared tracking pixels * Modified indenting according to coding standard * Code review fixes * Marked required parameters * Remove unused library from package.json Fix unit tests after refactoring --- .gitignore | 3 + integrationExamples/gpt/pbjs_example_gpt.html | 7 + package.json | 3 + src/adapters/wideorbit.js | 213 ++++++++ test/spec/adapters/wideorbit_spec.js | 491 ++++++++++++++++++ 5 files changed, 717 insertions(+) create mode 100644 src/adapters/wideorbit.js create mode 100644 test/spec/adapters/wideorbit_spec.js diff --git a/.gitignore b/.gitignore index c0a1a2f8ba4..8486ee1a189 100644 --- a/.gitignore +++ b/.gitignore @@ -66,3 +66,6 @@ crashlytics-build.properties #zip files (for releases) *.zip + +# TypeScript typings +typings/ diff --git a/integrationExamples/gpt/pbjs_example_gpt.html b/integrationExamples/gpt/pbjs_example_gpt.html index 5384bf6cc0a..540f48be538 100644 --- a/integrationExamples/gpt/pbjs_example_gpt.html +++ b/integrationExamples/gpt/pbjs_example_gpt.html @@ -240,6 +240,13 @@ publisher_id: 5000563, // REQUIRED int or str publisher ID. To get one, register at https://control.adequant.com bidfloor: 0.01, // OPTIONAL float bid floor in $ CPM } + }, + { + bidder: 'wideorbit', + params: { + pbId: 123, // REQUIRED Publisher Id, + pId: 123456 // REQUIRED Placement Id + } } ] } diff --git a/package.json b/package.json index 2df9883ffe2..3e47727c1e2 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "yieldbot", "nginad", "brightcom", + "wideorbit", { "appnexus": { "alias": "brealtime" @@ -100,6 +101,7 @@ "mocha": "^1.21.4", "open": "0.0.5", "phantomjs": "^1.9.18", + "querystringify": "0.0.3", "raw-loader": "^0.5.1", "redis": "^0.12.1", "requirejs": "^2.1.20", @@ -107,6 +109,7 @@ "sinon": "^1.12.1", "string-replace-webpack-plugin": "0.0.3", "uglify-js": "^2.4.15", + "url-parse": "^1.0.5", "webpack": "^1.12.3", "webpack-stream": "^3.1.0", "yargs": "^1.3.1" diff --git a/src/adapters/wideorbit.js b/src/adapters/wideorbit.js new file mode 100644 index 00000000000..e8431cab7bf --- /dev/null +++ b/src/adapters/wideorbit.js @@ -0,0 +1,213 @@ +var bidfactory = require('../bidfactory.js'), + bidmanager = require('../bidmanager.js'), + utils = require('../utils.js'), + adloader = require('../adloader'); + +var WideOrbitAdapter = function WideOrbitAdapter() { + var pageImpression = 'JSAdservingMP.ashx?pc={pc}&pbId={pbId}&clk=&exm=&jsv=1.0&tsv=1.0&cts={cts}&arp=0&fl=0&vitp=&vit=&jscb=window.pbjs.handleWideOrbitCallback&url=&fp=&oid=&exr=&mraid=&apid=&apbndl=&mpp=0&uid=&cb={cb}&hb=1', + pageRepeatCommonParam = '&gid{o}={gid}&pp{o}=&clk{o}=&rpos{o}={rpos}&ecpm{o}={ecpm}&ntv{o}=&ntl{o}=&adsid{o}=', + pageRepeatParamId = '&pId{o}={pId}&rank{o}={rank}', + pageRepeatParamNamed = '&wsName{o}={wsName}&wName{o}={wName}&rank{o}={rank}&bfDim{o}={width}x{height}&subp{o}={subp}', + base = (window.location.protocol) + '//p{pbId}.atemda.com/', + bids, + adapterName = 'wideorbit'; + + function _fixParamNames(param) { + if (!param) { + return; + } + + var properties = ['site', 'page', 'width', 'height', 'rank', 'subPublisher', 'ecpm', 'atf', 'pId', 'pbId'], + prop; + + utils._each(properties, function (correctName) { + for (prop in param) { + if (param.hasOwnProperty(prop) && prop.toLowerCase() === correctName.toLowerCase()) { + param[correctName] = param[prop]; + break; + } + } + }); + } + + function _setParam(str, param, value) { + var pattern = new RegExp('{' + param + '}', 'g'); + + if (value === true) { + value = 1; + } + if (value === false) { + value = 0; + } + return str.replace(pattern, value); + } + + function _setParams(str, keyValuePairs) { + utils._each(keyValuePairs, function (keyValuePair) { + str = _setParam(str, keyValuePair[0], keyValuePair[1]); + }); + return str; + } + + function _setCommonParams(pos, params) { + return _setParams(pageRepeatCommonParam, [ + ['o', pos], + ['gid', encodeURIComponent(params.tagId)], + ['rpos', params.atf ? 1001 : 0], + ['ecpm', params.ecpm || ''] + ]); + } + + function _getRankParam(rank, pos) { + return rank || pos; + } + + function _setupIdPlacementParameters(pos, params) { + return _setParams(pageRepeatParamId, [ + ['o', pos], + ['pId', params.pId], + ['rank', _getRankParam(params.rank, pos)] + ]); + } + + function _setupNamedPlacementParameters(pos, params) { + return _setParams(pageRepeatParamNamed, [ + ['o', pos], + ['wsName', encodeURIComponent(decodeURIComponent(params.site))], + ['wName', encodeURIComponent(decodeURIComponent(params.page))], + ['width', params.width], + ['height', params.height], + ['subp', params.subPublisher ? encodeURIComponent(decodeURIComponent(params.subPublisher)) : ''], + ['rank', _getRankParam(params.rank, pos)] + ]); + } + + function _setupAdCall(publisherId, placementCount, placementsComponent) { + return _setParams(base + pageImpression, [ + ['pbId', publisherId], + ['pc', placementCount], + ['cts', new Date().getTime()], + ['cb', Math.floor(Math.random() * 100000000)] + ]) + placementsComponent; + } + + function _setupPlacementParameters(pos, params) { + var commonParams = _setCommonParams(pos, params); + + if (params.pId) { + return _setupIdPlacementParameters(pos, params) + commonParams; + } + + return _setupNamedPlacementParameters(pos, params) + commonParams; + } + + function _callBids(params) { + var publisherId, + bidUrl = '', + i; + + bids = params.bids || []; + + for (i = 0; i < bids.length; i++) { + var requestParams = bids[i].params; + + requestParams.tagId = bids[i].placementCode; + + _fixParamNames(requestParams); + + publisherId = requestParams.pbId; + bidUrl += _setupPlacementParameters(i, requestParams); + } + + bidUrl = _setupAdCall(publisherId, bids.length, bidUrl); + + utils.logMessage('Calling WO: ' + bidUrl); + + adloader.loadScript(bidUrl); + } + + function _processUserMatchings(userMatchings) { + var headElem = document.getElementsByTagName('head')[0], + createdElem; + + utils._each(userMatchings, function (userMatching) { + switch (userMatching.Type) { + case 'redirect': + createdElem = document.createElement('img'); + break; + case 'iframe': + createdElem = utils.createInvisibleIframe(); + break; + case 'javascript': + createdElem = document.createElement('script'); + createdElem.type = 'text/javascript'; + createdElem.async = true; + break; + } + createdElem.src = decodeURIComponent(userMatching.Url); + headElem.insertBefore(createdElem, headElem.firstChild); + }); + } + + function _getBidResponse(id, placements) { + var i; + + for (i = 0; i < placements.length; i++) { + if (placements[i].ExtPlacementId === id) { + return placements[i]; + } + } + } + + function _isUrl(scr) { + return scr.slice(0, 6) === "http:/" || scr.slice(0, 7) === "https:/" || scr.slice(0, 2) === "//"; + } + + function _buildAdCode(placement) { + var adCode = placement.Source, pixelTag; + + utils._each(placement.TrackingCodes, function (trackingCode) { + if (_isUrl(trackingCode)) { + pixelTag = ''; + } else { + pixelTag = trackingCode; + } + adCode = pixelTag + adCode; + }); + + return adCode; + } + + window.pbjs = window.pbjs || {}; + window.pbjs.handleWideOrbitCallback = function (response) { + var bidResponse, + bidObject; + + utils.logMessage('WO response. Placements: ' + response.Placements.length); + + _processUserMatchings(response.UserMatchings); + + utils._each(bids, function (bid) { + bidResponse = _getBidResponse(bid.placementCode, response.Placements); + + if (bidResponse && bidResponse.Type === 'DirectHTML') { + bidObject = bidfactory.createBid(1); + bidObject.cpm = bidResponse.Bid; + bidObject.ad = _buildAdCode(bidResponse); + bidObject.width = bidResponse.Width; + bidObject.height = bidResponse.Height; + } else { + bidObject = bidfactory.createBid(2); + } + + bidObject.bidderCode = adapterName; + bidmanager.addBidResponse(bid.placementCode, bidObject); + }); + }; + + return { + callBids: _callBids + }; +}; + +module.exports = WideOrbitAdapter; diff --git a/test/spec/adapters/wideorbit_spec.js b/test/spec/adapters/wideorbit_spec.js new file mode 100644 index 00000000000..d86e23fa006 --- /dev/null +++ b/test/spec/adapters/wideorbit_spec.js @@ -0,0 +1,491 @@ +describe('wideorbit adapter tests', function () { + + var expect = require('chai').expect; + var urlParse = require('url-parse'); + + // FYI: querystringify will perform encoding/decoding + var querystringify = require('querystringify'); + + var adapter = require('src/adapters/wideorbit'); + var adLoader = require('src/adloader'); + var bidmanager = require('src/bidmanager'); + + describe('creation of bid url', function () { + + var spyLoadScript; + + beforeEach(function () { + spyLoadScript = sinon.spy(adLoader, 'loadScript'); + }); + + afterEach(function () { + spyLoadScript.restore(); + }); + + it('should be called only once', function () { + + var params = { + bidderCode: 'wideorbit', + bids: [ + { + bidder: 'wideorbit', + params: { + pbId: 1, + pId: 101 + }, + placementCode: 'div-gpt-ad-12345-1' + }, + { + bidder: 'wideorbit', + params: { + pbId: 1, + site: 'Site 1', + page: 'Page 1', + width: 100, + height: 200, + subPublisher: 'Sub Publisher 1' + }, + placementCode: 'div-gpt-ad-12345-2' + } + ] + }; + + adapter().callBids(params); + + sinon.assert.calledOnce(spyLoadScript); + + }); + + it('should fix parameters name', function () { + + var params = { + bidderCode: 'wideorbit', + bids: [ + { + bidder: 'wideorbit', + params: { + PBiD: 1, + PID: 101 + }, + placementCode: 'div-gpt-ad-12345-1' + }, + { + bidder: 'wideorbit', + params: { + pbid: 1, + SiTe: 'Site 1', + Page: 'Page 1', + widTH: 100, + HEIGHT: 200, + SUBPublisher: 'Sub Publisher 1' + }, + placementCode: 'div-gpt-ad-12345-2' + } + ] + }; + + adapter().callBids(params); + + var bidUrl = spyLoadScript.getCall(0).args[0]; + + sinon.assert.calledWith(spyLoadScript, bidUrl); + + var parsedBidUrl = urlParse(bidUrl); + var parsedBidUrlQueryString = querystringify.parse(parsedBidUrl.query); + + expect(parsedBidUrl.hostname).to.equal('p1.atemda.com') + expect(parsedBidUrl.pathname).to.equal('/JSAdservingMP.ashx') + expect(parsedBidUrlQueryString).to.have.property('pc').and.to.equal('2'); + expect(parsedBidUrlQueryString).to.have.property('pbId').and.to.equal('1'); + expect(parsedBidUrlQueryString).to.have.property('jsv').and.to.equal('1.0'); + expect(parsedBidUrlQueryString).to.have.property('tsv').and.to.equal('1.0'); + expect(parsedBidUrlQueryString).to.have.property('cts').to.have.length.above(0); + expect(parsedBidUrlQueryString).to.have.property('arp').and.to.equal('0'); + expect(parsedBidUrlQueryString).to.have.property('fl').and.to.equal('0'); + expect(parsedBidUrlQueryString).to.have.property('jscb').and.to.equal('window.pbjs.handleWideOrbitCallback'); + expect(parsedBidUrlQueryString).to.have.property('mpp').and.to.equal('0'); + expect(parsedBidUrlQueryString).to.have.property('cb').to.have.length.above(0); + expect(parsedBidUrlQueryString).to.have.property('hb').and.to.equal('1'); + + expect(parsedBidUrlQueryString).to.have.property('gid0').and.to.equal('div-gpt-ad-12345-1'); + expect(parsedBidUrlQueryString).to.have.property('rpos0').and.to.equal('0'); + expect(parsedBidUrlQueryString).to.have.property('ecpm0').and.to.equal(''); + + expect(parsedBidUrlQueryString).to.have.property('gid1').and.to.equal('div-gpt-ad-12345-2'); + expect(parsedBidUrlQueryString).to.have.property('rpos1').and.to.equal('0'); + expect(parsedBidUrlQueryString).to.have.property('ecpm1').and.to.equal(''); + + expect(parsedBidUrlQueryString).to.have.property('pId0').and.to.equal('101'); + expect(parsedBidUrlQueryString).to.have.property('rank0').and.to.equal('0'); + + expect(parsedBidUrlQueryString).to.have.property('wsName1').and.to.equal('Site 1'); + expect(parsedBidUrlQueryString).to.have.property('wName1').and.to.equal('Page 1'); + expect(parsedBidUrlQueryString).to.have.property('rank1').and.to.equal('1'); + expect(parsedBidUrlQueryString).to.have.property('bfDim1').and.to.equal('100x200'); + expect(parsedBidUrlQueryString).to.have.property('subp1').and.to.equal('Sub Publisher 1'); + + }); + + describe('placement by name', function () { + + it('should be called with specific parameters for two bids', function () { + + var params = { + bidderCode: 'wideorbit', + bids: [ + { + bidder: 'wideorbit', + params: { + pbId: 1, + site: 'Site 1', + page: 'Page 1', + width: 100, + height: 200, + subPublisher: 'Sub Publisher 1', + atf: true + }, + placementCode: 'div-gpt-ad-12345-1' + }, + { + bidder: 'wideorbit', + params: { + pbId: 1, + site: 'Site 2', + page: 'Page 2', + width: 200, + height: 300, + rank: 123, + ecpm: 1.8 + }, + placementCode: 'div-gpt-ad-12345-2' + } + ] + }; + + adapter().callBids(params); + + var bidUrl = spyLoadScript.getCall(0).args[0]; + + sinon.assert.calledWith(spyLoadScript, bidUrl); + + var parsedBidUrl = urlParse(bidUrl); + var parsedBidUrlQueryString = querystringify.parse(parsedBidUrl.query); + + expect(parsedBidUrl.hostname).to.equal('p1.atemda.com') + expect(parsedBidUrl.pathname).to.equal('/JSAdservingMP.ashx') + expect(parsedBidUrlQueryString).to.have.property('pc').and.to.equal('2'); + expect(parsedBidUrlQueryString).to.have.property('pbId').and.to.equal('1'); + expect(parsedBidUrlQueryString).to.have.property('jsv').and.to.equal('1.0'); + expect(parsedBidUrlQueryString).to.have.property('tsv').and.to.equal('1.0'); + expect(parsedBidUrlQueryString).to.have.property('cts').to.have.length.above(0); + expect(parsedBidUrlQueryString).to.have.property('arp').and.to.equal('0'); + expect(parsedBidUrlQueryString).to.have.property('fl').and.to.equal('0'); + expect(parsedBidUrlQueryString).to.have.property('jscb').and.to.equal('window.pbjs.handleWideOrbitCallback'); + expect(parsedBidUrlQueryString).to.have.property('mpp').and.to.equal('0'); + expect(parsedBidUrlQueryString).to.have.property('cb').to.have.length.above(0); + expect(parsedBidUrlQueryString).to.have.property('hb').and.to.equal('1'); + + expect(parsedBidUrlQueryString).to.have.property('gid0').and.to.equal('div-gpt-ad-12345-1'); + expect(parsedBidUrlQueryString).to.have.property('rpos0').and.to.equal('1001'); + expect(parsedBidUrlQueryString).to.have.property('ecpm0').and.to.equal(''); + + expect(parsedBidUrlQueryString).to.have.property('gid1').and.to.equal('div-gpt-ad-12345-2'); + expect(parsedBidUrlQueryString).to.have.property('rpos1').and.to.equal('0'); + expect(parsedBidUrlQueryString).to.have.property('ecpm1').and.to.equal('1.8'); + + expect(parsedBidUrlQueryString).to.have.property('wsName0').and.to.equal('Site 1'); + expect(parsedBidUrlQueryString).to.have.property('wName0').and.to.equal('Page 1'); + expect(parsedBidUrlQueryString).to.have.property('rank0').and.to.equal('0'); + expect(parsedBidUrlQueryString).to.have.property('bfDim0').and.to.equal('100x200'); + expect(parsedBidUrlQueryString).to.have.property('subp0').and.to.equal('Sub Publisher 1'); + + expect(parsedBidUrlQueryString).to.have.property('wsName1').and.to.equal('Site 2'); + expect(parsedBidUrlQueryString).to.have.property('wName1').and.to.equal('Page 2'); + expect(parsedBidUrlQueryString).to.have.property('rank1').and.to.equal('123'); + expect(parsedBidUrlQueryString).to.have.property('bfDim1').and.to.equal('200x300'); + expect(parsedBidUrlQueryString).to.have.property('subp1').and.to.equal(''); + + }); + + }); + + describe('placement by id', function () { + + it('should be called with specific parameters for two bids', function () { + + var params = { + bidderCode: 'wideorbit', + bids: [ + { + bidder: 'wideorbit', + params: { + pbId: 1, + pId: 101, + atf: true, + ecpm: 0.8 + }, + placementCode: 'div-gpt-ad-12345-1' + }, + { + bidder: 'wideorbit', + params: { + pbId: 1, + pId: 102, + rank: 123 + }, + placementCode: 'div-gpt-ad-12345-2' + } + ] + }; + + adapter().callBids(params); + + var bidUrl = spyLoadScript.getCall(0).args[0]; + + sinon.assert.calledWith(spyLoadScript, bidUrl); + + var parsedBidUrl = urlParse(bidUrl); + var parsedBidUrlQueryString = querystringify.parse(parsedBidUrl.query); + + expect(parsedBidUrl.hostname).to.equal('p1.atemda.com') + expect(parsedBidUrl.pathname).to.equal('/JSAdservingMP.ashx') + expect(parsedBidUrlQueryString).to.have.property('pc').and.to.equal('2'); + expect(parsedBidUrlQueryString).to.have.property('pbId').and.to.equal('1'); + expect(parsedBidUrlQueryString).to.have.property('jsv').and.to.equal('1.0'); + expect(parsedBidUrlQueryString).to.have.property('tsv').and.to.equal('1.0'); + expect(parsedBidUrlQueryString).to.have.property('cts').to.have.length.above(0); + expect(parsedBidUrlQueryString).to.have.property('arp').and.to.equal('0'); + expect(parsedBidUrlQueryString).to.have.property('fl').and.to.equal('0'); + expect(parsedBidUrlQueryString).to.have.property('jscb').and.to.equal('window.pbjs.handleWideOrbitCallback'); + expect(parsedBidUrlQueryString).to.have.property('mpp').and.to.equal('0'); + expect(parsedBidUrlQueryString).to.have.property('cb').to.have.length.above(0); + expect(parsedBidUrlQueryString).to.have.property('hb').and.to.equal('1'); + + expect(parsedBidUrlQueryString).to.have.property('gid0').and.to.equal('div-gpt-ad-12345-1'); + expect(parsedBidUrlQueryString).to.have.property('rpos0').and.to.equal('1001'); + expect(parsedBidUrlQueryString).to.have.property('ecpm0').and.to.equal('0.8'); + + expect(parsedBidUrlQueryString).to.have.property('gid1').and.to.equal('div-gpt-ad-12345-2'); + expect(parsedBidUrlQueryString).to.have.property('rpos1').and.to.equal('0'); + expect(parsedBidUrlQueryString).to.have.property('ecpm1').and.to.equal(''); + + expect(parsedBidUrlQueryString).to.have.property('pId0').and.to.equal('101'); + expect(parsedBidUrlQueryString).to.have.property('rank0').and.to.equal('0'); + + expect(parsedBidUrlQueryString).to.have.property('pId1').and.to.equal('102'); + expect(parsedBidUrlQueryString).to.have.property('rank1').and.to.equal('123'); + + }); + + }); + + }); + + describe('handling of the callback response', function () { + + var placements = [ + { + ExtPlacementId: 'div-gpt-ad-12345-1', + Type: 'DirectHTML', + Bid: 1.3, + Width: 50, + Height: 100, + Source: '
    The AD 1 itself...
    ', + TrackingCodes: [ + 'https://www.admeta.com/1.gif' + ] + }, + { + ExtPlacementId: 'div-gpt-ad-12345-2', + Type: 'DirectHTML', + Bid: 1.5, + Width: 100, + Height: 200, + Source: '
    The AD 2 itself...
    ', + TrackingCodes: [ + 'http://www.admeta.com/2a.gif', + '' + ] + }, + { + ExtPlacementId: 'div-gpt-ad-12345-3', + Type: 'Other', + Bid: 1.7, + Width: 150, + Height: 250, + Source: '
    The AD 3 itself...
    ', + TrackingCodes: [ + 'http://www.admeta.com/3.gif' + ] + } + ]; + + it('callback function should exist', function () { + expect(pbjs.handleWideOrbitCallback).to.exist.and.to.be.a('function'); + }); + + it('bidmanager.addBidResponse should be called thrice with correct arguments', function () { + + var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); + + var params = { + bidderCode: 'wideorbit', + bids: [ + { + bidder: 'wideorbit', + params: { + pbId: 1, + pId: 101 + }, + placementCode: 'div-gpt-ad-12345-1' + }, + { + bidder: 'wideorbit', + params: { + pbId: 1, + site: 'Site 1', + page: 'Page 1', + width: 100, + height: 200, + subPublisher: 'Sub Publisher 1' + }, + placementCode: 'div-gpt-ad-12345-2' + }, + { + bidder: 'wideorbit', + params: { + pbId: 1, + pId: 102 + }, + placementCode: 'div-gpt-ad-12345-3' + }, + ] + }; + + var response = { + UserMatchings: [ + { + Type: 'redirect', + Url: 'http%3A%2F%2Fwww.admeta.com%2F1.gif' + } + ], + Placements: placements + }; + + adapter().callBids(params); + pbjs.handleWideOrbitCallback(response); + + var bidPlacementCode1 = stubAddBidResponse.getCall(0).args[0]; + var bidObject1 = stubAddBidResponse.getCall(0).args[1]; + var bidPlacementCode2 = stubAddBidResponse.getCall(1).args[0]; + var bidObject2 = stubAddBidResponse.getCall(1).args[1]; + var bidPlacementCode3 = stubAddBidResponse.getCall(2).args[0]; + var bidObject3 = stubAddBidResponse.getCall(2).args[1]; + + expect(bidPlacementCode1).to.equal('div-gpt-ad-12345-1'); + expect(bidObject1.cpm).to.equal(1.3); + expect(bidObject1.ad).to.equal('
    The AD 1 itself...
    '); + expect(bidObject1.width).to.equal(50); + expect(bidObject1.height).to.equal(100); + expect(bidObject1.getStatusCode()).to.equal(1); + expect(bidObject1.bidderCode).to.equal('wideorbit'); + + expect(bidPlacementCode2).to.equal('div-gpt-ad-12345-2'); + expect(bidObject2.cpm).to.equal(1.50); + expect(bidObject2.ad).to.equal('
    The AD 2 itself...
    '); + expect(bidObject2.width).to.equal(100); + expect(bidObject2.height).to.equal(200); + expect(bidObject2.getStatusCode()).to.equal(1); + expect(bidObject2.bidderCode).to.equal('wideorbit'); + + expect(bidPlacementCode3).to.equal('div-gpt-ad-12345-3'); + expect(bidObject3.getStatusCode()).to.equal(2); + expect(bidObject3.bidderCode).to.equal('wideorbit'); + + sinon.assert.calledWith(stubAddBidResponse, bidPlacementCode1, bidObject1); + sinon.assert.calledWith(stubAddBidResponse, bidPlacementCode2, bidObject2); + sinon.assert.calledWith(stubAddBidResponse, bidPlacementCode3, bidObject3); + + sinon.assert.calledThrice(stubAddBidResponse); + + stubAddBidResponse.restore(); + + }); + + it('should append an image to the head when type is set to redirect', function () { + + var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); + + var response = { + UserMatchings: [ + { + Type: 'redirect', + Url: 'http%3A%2F%2Fwww.admeta.com%2F1.gif' + } + ], + Placements: placements + }; + + pbjs.handleWideOrbitCallback(response); + + var imgElement = document.querySelectorAll("head img")[0]; + + expect(imgElement).to.exist; + expect(imgElement.src).to.equal('http://www.admeta.com/1.gif'); + + stubAddBidResponse.restore(); + }); + + it('should append an iframe to the head when type is set to iframe', function () { + + var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); + + var response = { + UserMatchings: [ + { + Type: 'iframe', + Url: 'http%3A%2F%2Fwww.admeta.com%2F1.ashx' + } + ], + Placements: placements + }; + + pbjs.handleWideOrbitCallback(response); + + var iframeElement = document.querySelectorAll("head iframe")[0]; + + expect(iframeElement).to.exist; + expect(iframeElement.src).to.equal('http://www.admeta.com/1.ashx'); + + stubAddBidResponse.restore(); + + }); + + it('should append an script to the head when type is set to javascript', function () { + + var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); + + var response = { + UserMatchings: [ + { + Type: 'javascript', + Url: 'http%3A%2F%2Fwww.admeta.com%2F1.js' + } + ], + Placements: placements + }; + + pbjs.handleWideOrbitCallback(response); + + var scriptElement = document.querySelectorAll("head script")[0]; + + expect(scriptElement).to.exist; + expect(scriptElement.src).to.equal('http://www.admeta.com/1.js'); + + stubAddBidResponse.restore(); + }); + + }); + +}); + \ No newline at end of file From a9252fae8b727c6a0c800b2adebbf81997bd671c Mon Sep 17 00:00:00 2001 From: Matt Lane Date: Tue, 7 Jun 2016 11:23:21 -0700 Subject: [PATCH 148/160] Pass Deal IDs on bids to ad server as targeting data (#390) If a bid response contains a dealId, its ad server targeting will receive a key with the form `hb_deal_`. Bids with the highest cpm still always win whether they contain a deal or not. If a winning bid does have a dealId, its ad server targeting will also have a key of `hb_deal` with the value of the deal id. Deals are always sent to the server, regardless of whether `enableSendAllBids` is on or off. * Construct required key/value pairs for bids with deals * Always attach bids with deals to targeting * Test addBidResponse places dealIds in adserver targeting --- src/bidmanager.js | 5 +++++ src/prebid.js | 26 +++++++++++++++++++++++++- test/spec/bidmanager_spec.js | 12 ++++++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) diff --git a/src/bidmanager.js b/src/bidmanager.js index e0f9adbe35e..dcc6627faeb 100644 --- a/src/bidmanager.js +++ b/src/bidmanager.js @@ -96,6 +96,11 @@ exports.addBidResponse = function (adUnitCode, bid) { var keyValues = {}; if (bid.bidderCode && bid.cpm !== 0) { keyValues = getKeyValueTargetingPairs(bid.bidderCode, bid); + + if (bid.dealId) { + keyValues[`hb_deal_${bid.bidderCode}`] = bid.dealId; + } + bid.adserverTargeting = keyValues; } diff --git a/src/prebid.js b/src/prebid.js index 311ead9e640..7f1e5b32acb 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -155,6 +155,11 @@ function getWinningBidTargeting() { timeToRespond: 0 })); + // winning bids with deals need an hb_deal targeting key + winners + .filter(bid => bid.dealId) + .map(bid => bid.adserverTargeting.hb_deal = bid.dealId); + winners = winners.map(winner => { return { [winner.adUnitCode]: Object.keys(winner.adserverTargeting, key => key) @@ -171,6 +176,22 @@ function getWinningBidTargeting() { return winners; } +function getDealTargeting() { + const dealTargeting = pbjs._bidsReceived.filter(bid => bid.dealId).map(bid => { + const dealKey = `hb_deal_${bid.bidderCode}`; + return { + [bid.adUnitCode]: CONSTANTS.TARGETING_KEYS.map(key => { + return { + [`${key}_${bid.bidderCode}`.substring(0, 20)]: [bid.adserverTargeting[key]] + }; + }) + .concat({ [dealKey]: [bid.adserverTargeting[dealKey]] }) + }; + }); + + return dealTargeting; +} + function getBidLandscapeTargeting() { const standardKeys = CONSTANTS.TARGETING_KEYS; @@ -188,7 +209,10 @@ function getBidLandscapeTargeting() { } function getAllTargeting() { - return getWinningBidTargeting().concat(pb_sendAllBids ? getBidLandscapeTargeting() : []); + let targeting = getWinningBidTargeting(); + // deals are always attached to targeting + targeting = getDealTargeting().concat(targeting); + return targeting.concat(pb_sendAllBids ? getBidLandscapeTargeting() : []); } ////////////////////////////////// diff --git a/test/spec/bidmanager_spec.js b/test/spec/bidmanager_spec.js index b5166ab6917..17498bd67e1 100644 --- a/test/spec/bidmanager_spec.js +++ b/test/spec/bidmanager_spec.js @@ -390,5 +390,17 @@ describe('bidmanager.js', function () { registeredBid = pbjs._bidsReceived.pop(); assert.equal(registeredBid.pbDg, expectedIncrement, '20+ caps at 20.00'); }); + + it('should place dealIds in adserver targeting', () => { + const bid = Object.assign({}, + bidfactory.createBid(2), + fixtures.getBidResponses()[0] + ); + + bid.dealId = "test deal"; + bidmanager.addBidResponse(bid.adUnitCode, bid); + const addedBid = pbjs._bidsReceived.pop(); + assert.equal(addedBid.adserverTargeting[`hb_deal_${bid.bidderCode}`], bid.dealId, 'dealId placed in adserverTargeting'); + }); }); }); From d7776f5e27dfccf617015786d09ba21e88b3a821 Mon Sep 17 00:00:00 2001 From: Chris Naegelin Date: Fri, 20 May 2016 16:10:45 +0200 Subject: [PATCH 149/160] RTK Aardvark Header Bidding Adapter --- integrationExamples/gpt/pbjs_example_gpt.html | 14 +++ package.json | 1 + src/adapters/aardvark.js | 106 ++++++++++++++++++ 3 files changed, 121 insertions(+) create mode 100644 src/adapters/aardvark.js diff --git a/integrationExamples/gpt/pbjs_example_gpt.html b/integrationExamples/gpt/pbjs_example_gpt.html index 540f48be538..72933fda08f 100644 --- a/integrationExamples/gpt/pbjs_example_gpt.html +++ b/integrationExamples/gpt/pbjs_example_gpt.html @@ -81,6 +81,20 @@ params: { placementId: 'TO ADD' } + }, { + bidder:"aardvark", + params: { + ai: "TO ADD", + sc: "TO ADD" + } + }, { + bidder: 'pulsepoint', + params: { + "cf": "728X90", + "cp": "558467", + "ct": "317348" + } + rtkid: "6372" }, { bidder: 'pubmatic', params: { diff --git a/package.json b/package.json index 3e47727c1e2..d3f1da1a039 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "url": "https://github.com/prebid/Prebid.js.git" }, "adapters": [ + "aardvark", "adequant", "adform", "aol", diff --git a/src/adapters/aardvark.js b/src/adapters/aardvark.js new file mode 100644 index 00000000000..145b72d9ec7 --- /dev/null +++ b/src/adapters/aardvark.js @@ -0,0 +1,106 @@ +var utils = require('../utils.js'); +var bidfactory = require('../bidfactory.js'); +var bidmanager = require('../bidmanager.js'); +var adloader = require('../adloader'); + + +/** + * Adapter for requesting bids from RTK Aardvark + * To request an RTK Aardvark Header bidding account + * or for additional integration support please contact sales@rtk.io + */ + +var AardvarkAdapter = function AardvarkAdapter() { + + function _callBids(params) { + var rtkBids = params.bids || []; + + _requestBids(rtkBids); + } + + function _requestBids(bidReqs) { + // build bid request object + var ref = window.top.location.host; + var ai = ""; + var shortcodes = []; + + //build bid URL for RTK + utils._each(bidReqs, function (bid) { + ai = utils.getBidIdParamater('ai', bid.params); + var sc = utils.getBidIdParamater('sc', bid.params); + shortcodes.push(sc); + }); + + var scURL = ""; + + if (shortcodes.length > 1) { + scURL = shortcodes.join("_"); + } else { + scURL = shortcodes[0]; + } + + var scriptUrl = '//thor.rtk.io/' + ai + "/" + scURL + "/aardvark/?jsonp=window.pbjs.aardvarkResponse&rtkreferer=" + ref; + adloader.loadScript(scriptUrl, null); + } + + //expose the callback to the global object: + window.pbjs.aardvarkResponse = function (rtkResponseObj) { + + //Get all initial Aardvark Bid Objects + var bidsObj = pbjs._bidsRequested.filter(function (bidder) { + return bidder.bidderCode === 'aardvark'; + })[0]; + + var returnedBidIDs = {}; + var placementIDmap = {}; + + if (rtkResponseObj.length > 0) { + rtkResponseObj.forEach(function (bid) { + + if (!bid.error) { + var currentBid = bidsObj.bids.filter(function (r) { + return r.params.sc === bid.id; + })[0]; + if (currentBid) { + var bidResponse = bidfactory.createBid(1); + bidResponse.bidderCode = "aardvark"; + bidResponse.cpm = bid.cpm; + bidResponse.ad = bid.adm; + bidResponse.ad += utils.createTrackPixelHtml(decodeURIComponent(bid.nurl)); + bidResponse.width = currentBid.sizes[0][0]; + bidResponse.height = currentBid.sizes[0][1]; + returnedBidIDs[bid.id] = currentBid.placementCode; + bidmanager.addBidResponse(currentBid.placementCode, bidResponse); + } + + } + + }); + + } + + //All bids are back - lets add a bid response for anything that did not receive a bid. + var initialSC = []; + bidsObj.bids.forEach(function (bid) { + initialSC.push(bid.params.sc); + placementIDmap[bid.params.sc] = bid.placementCode; + }); + + let difference = initialSC.filter(x => Object.keys(returnedBidIDs).indexOf(x) === -1); + + difference.forEach(function (shortcode) { + var bidResponse = bidfactory.createBid(2); + var placementcode = placementIDmap[shortcode]; + bidResponse.bidderCode = "aardvark"; + bidmanager.addBidResponse(placementcode, bidResponse); + }); + + + }; // aardvarkResponse + + return { + callBids: _callBids + }; +}; + +module.exports = AardvarkAdapter; From 617cc30d9540ef889f261a665c2f8e051150a12c Mon Sep 17 00:00:00 2001 From: Chris Naegelin Date: Fri, 20 May 2016 16:23:33 +0200 Subject: [PATCH 150/160] no message --- integrationExamples/gpt/pbjs_example_gpt.html | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/integrationExamples/gpt/pbjs_example_gpt.html b/integrationExamples/gpt/pbjs_example_gpt.html index 72933fda08f..efe12974695 100644 --- a/integrationExamples/gpt/pbjs_example_gpt.html +++ b/integrationExamples/gpt/pbjs_example_gpt.html @@ -87,15 +87,7 @@ ai: "TO ADD", sc: "TO ADD" } - }, { - bidder: 'pulsepoint', - params: { - "cf": "728X90", - "cp": "558467", - "ct": "317348" - } - rtkid: "6372" - }, { + }, { bidder: 'pubmatic', params: { publisherId: 'TO ADD', From 2bb9a60acb416618c0209c04ae8067d84084b544 Mon Sep 17 00:00:00 2001 From: Chris Naegelin Date: Fri, 20 May 2016 17:12:20 +0200 Subject: [PATCH 151/160] rtk sample --- integrationExamples/gpt/pbjs_example_rtk.html | 320 ++++++++++++++++++ 1 file changed, 320 insertions(+) create mode 100644 integrationExamples/gpt/pbjs_example_rtk.html diff --git a/integrationExamples/gpt/pbjs_example_rtk.html b/integrationExamples/gpt/pbjs_example_rtk.html new file mode 100644 index 00000000000..4dba15bebf1 --- /dev/null +++ b/integrationExamples/gpt/pbjs_example_rtk.html @@ -0,0 +1,320 @@ + + + + + + + + + + + + +

    Prebid.js Test

    + +
    + +
    + + +
    + +
    + + + + + + + From d8b179f63154c15d4361f57ff525f9cf4f9c0f16 Mon Sep 17 00:00:00 2001 From: Chris Naegelin Date: Tue, 24 May 2016 10:33:40 +0200 Subject: [PATCH 152/160] Requested changes --- integrationExamples/gpt/pbjs_example_gpt.html | 2 ++ src/adapters/aardvark.js | 12 +++++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/integrationExamples/gpt/pbjs_example_gpt.html b/integrationExamples/gpt/pbjs_example_gpt.html index efe12974695..59631ba9c84 100644 --- a/integrationExamples/gpt/pbjs_example_gpt.html +++ b/integrationExamples/gpt/pbjs_example_gpt.html @@ -84,7 +84,9 @@ }, { bidder:"aardvark", params: { + //The RTK Auction ID ai: "TO ADD", + //The RTK Ad Unit ID (shortcode) sc: "TO ADD" } }, { diff --git a/src/adapters/aardvark.js b/src/adapters/aardvark.js index 145b72d9ec7..3fde05edb27 100644 --- a/src/adapters/aardvark.js +++ b/src/adapters/aardvark.js @@ -19,8 +19,14 @@ var AardvarkAdapter = function AardvarkAdapter() { } function _requestBids(bidReqs) { - // build bid request object - var ref = window.top.location.host; + + try { + var ref = window.top.location.host; + } + catch (err) { + var ref = "thor.rtk.io"; + + } var ai = ""; var shortcodes = []; @@ -40,7 +46,7 @@ var AardvarkAdapter = function AardvarkAdapter() { } var scriptUrl = '//thor.rtk.io/' + ai + "/" + scURL + "/aardvark/?jsonp=window.pbjs.aardvarkResponse&rtkreferer=" + ref; - adloader.loadScript(scriptUrl, null); + adloader.loadScript(scriptUrl); } //expose the callback to the global object: From 002787e06a96d29d18ae0ca19da9b299335f0ca6 Mon Sep 17 00:00:00 2001 From: Chris Naegelin Date: Tue, 24 May 2016 10:46:39 +0200 Subject: [PATCH 153/160] unnecessary file --- integrationExamples/gpt/pbjs_example_rtk.html | 320 ------------------ 1 file changed, 320 deletions(-) delete mode 100644 integrationExamples/gpt/pbjs_example_rtk.html diff --git a/integrationExamples/gpt/pbjs_example_rtk.html b/integrationExamples/gpt/pbjs_example_rtk.html deleted file mode 100644 index 4dba15bebf1..00000000000 --- a/integrationExamples/gpt/pbjs_example_rtk.html +++ /dev/null @@ -1,320 +0,0 @@ - - - - - - - - - - - - -

    Prebid.js Test

    - -
    - -
    - - -
    - -
    - - - - - - - From d5b0e4f31784530abc49404c8a9d4c126ad3085e Mon Sep 17 00:00:00 2001 From: sekindo Date: Wed, 15 Jun 2016 00:47:55 +0300 Subject: [PATCH 154/160] Sekindo Prebid Adapter : (#355) * Sekindo Prebid Adaptor : * add placement id to error log * append iframe to head * refactor * use triple equality & adding sekindo to package.json * logging * frameDoc.open() --- integrationExamples/gpt/pbjs_example_gpt.html | 9 +- package.json | 1 + src/adapters/sekindo.js | 114 ++++++++++++++++++ 3 files changed, 123 insertions(+), 1 deletion(-) create mode 100755 src/adapters/sekindo.js diff --git a/integrationExamples/gpt/pbjs_example_gpt.html b/integrationExamples/gpt/pbjs_example_gpt.html index 59631ba9c84..e930f61b3a6 100644 --- a/integrationExamples/gpt/pbjs_example_gpt.html +++ b/integrationExamples/gpt/pbjs_example_gpt.html @@ -173,7 +173,14 @@ publisher_id: 5000563, // REQUIRED int or str publisher ID. To get one, register at https://control.adequant.com bidfloor: 0.01, // OPTIONAL float bid floor in $ CPM } - } + }, + { + bidder: 'sekindo', + params: { + spaceId: 14071, // REQUIRED int. To get one, contact http://www.sekindo.com + bidfloor: 0.2 // OPTIONAL float bid floor in $ CPM + } + } ] }, { code: 'div-gpt-ad-12345678-1', diff --git a/package.json b/package.json index d3f1da1a039..d360e016d87 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "pubmatic", "pulsepoint", "rubicon", + "sekindo", "sonobi", "sovrn", "springserve", diff --git a/src/adapters/sekindo.js b/src/adapters/sekindo.js new file mode 100755 index 00000000000..8c25f534cb3 --- /dev/null +++ b/src/adapters/sekindo.js @@ -0,0 +1,114 @@ +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 SekindoAdapter; +SekindoAdapter = function SekindoAdapter() { + + 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; + } + } + + pbjs.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.adId = response.adId; + 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('sekindo callback general error'); + } + } + }; + + function _requestBids(bid, callbackId, pubUrl) + { + //determine tag params + var spaceId = utils.getBidIdParamater('spaceId', bid.params); + var bidfloor = utils.getBidIdParamater('bidfloor', bid.params); + var protocol = ('https:' === document.location.protocol ? 's' : ''); + var scriptSrc = 'https://live.sekindo.com/live/liveView.php?'; + + scriptSrc = utils.tryAppendQueryString(scriptSrc, 's', spaceId); + scriptSrc = utils.tryAppendQueryString(scriptSrc, 'pubUrl', pubUrl); + scriptSrc = utils.tryAppendQueryString(scriptSrc, 'hbcb', callbackId); + scriptSrc = utils.tryAppendQueryString(scriptSrc, 'dcpmflr', bidfloor); + scriptSrc = utils.tryAppendQueryString(scriptSrc, 'hbto', pbjs.bidderTimeout); + scriptSrc = utils.tryAppendQueryString(scriptSrc, 'protocol', protocol); + + var html = ''; + + var iframe = utils.createInvisibleIframe(); + iframe.id = 'skIfr_'+callbackId; + + var elToAppend = document.getElementsByTagName('head')[0]; + //insert the iframe into document + elToAppend.insertBefore(iframe, elToAppend.firstChild); + + var iframeDoc = utils.getIframeDocument(iframe); + iframeDoc.open(); + iframeDoc.write(html); + iframeDoc.close(); + } + + return { + callBids: _callBids + }; +}; + + +module.exports = SekindoAdapter; + From c951eac64b181ff5557f399bc795c88af97968d1 Mon Sep 17 00:00:00 2001 From: Seth Yates Date: Wed, 15 Jun 2016 15:17:36 +1000 Subject: [PATCH 155/160] feat(krux): add Krux Link adapter (#384) --- integrationExamples/gpt/pbjs_example_gpt.html | 50 ++++++------ package.json | 1 + src/adapters/kruxlink.js | 79 +++++++++++++++++++ 3 files changed, 106 insertions(+), 24 deletions(-) create mode 100644 src/adapters/kruxlink.js diff --git a/integrationExamples/gpt/pbjs_example_gpt.html b/integrationExamples/gpt/pbjs_example_gpt.html index e930f61b3a6..477f5319a1b 100644 --- a/integrationExamples/gpt/pbjs_example_gpt.html +++ b/integrationExamples/gpt/pbjs_example_gpt.html @@ -1,9 +1,8 @@ + - - +Prebid.js integration example - - - -

    Prebid.js Test

    @@ -405,7 +406,7 @@

    Prebid.js Test

    -
    +
    + + diff --git a/package.json b/package.json index d360e016d87..19395e42a25 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "aol", "appnexus", "indexExchange", + "kruxlink", "openx", "pubmatic", "pulsepoint", diff --git a/src/adapters/kruxlink.js b/src/adapters/kruxlink.js new file mode 100644 index 00000000000..547f502478f --- /dev/null +++ b/src/adapters/kruxlink.js @@ -0,0 +1,79 @@ +var bidfactory = require('../bidfactory.js'); +var bidmanager = require('../bidmanager.js'); +var adloader = require('../adloader.js'); + +function _qs(key, value) { + return encodeURIComponent(key) + '=' + encodeURIComponent(value); +} + +function _makeBidResponse(placementCode, bid) { + var bidResponse = bidfactory.createBid(bid !== undefined ? 1 : 2); + bidResponse.bidderCode = 'kruxlink'; + if (bid !== undefined) { + bidResponse.cpm = bid.price; + bidResponse.ad = bid.adm; + bidResponse.width = bid.w; + bidResponse.height = bid.h; + } + bidmanager.addBidResponse(placementCode, bidResponse); +} + +function _makeCallback(id, placements) { + var callback = '_kruxlink_' + id; + pbjs[callback] = function(response) { + // Clean up our callback + delete pbjs[callback]; + + // Add in the bid respones + for (var i = 0; i < response.seatbid.length; i++) { + var seatbid = response.seatbid[i]; + for (var j = 0; j < seatbid.bid.length; j++) { + var bid = seatbid.bid[j]; + _makeBidResponse(placements[bid.impid], bid); + delete placements[bid.impid]; + } + } + + // Add any no-bids remaining + for (var placementCode in placements) { + if (placements.hasOwnProperty(placementCode)) { + _makeBidResponse(placementCode); + } + } + }; + + return 'pbjs.' + callback; +} + +function _callBids(params) { + var impids = []; + var placements = {}; + + var bids = params.bids || []; + for (var i = 0; i < bids.length; i++) { + var bidRequest = bids[i]; + var bidRequestParams = bidRequest.params || {}; + var impid = bidRequestParams.impid; + placements[impid] = bidRequest.placementCode; + + impids.push(impid); + } + + var callback = _makeCallback(params.bidderRequestId, placements); + var qs = [ + _qs('id', params.bidderRequestId), + _qs('u', window.location.href), + _qs('impid', impids.join(',')), + _qs('calltype', 'pbd'), + _qs('callback', callback) + ]; + var url = 'https://link.krxd.net/hb?' + qs.join('&'); + + adloader.loadScript(url); +} + +module.exports = function KruxAdapter() { + return { + callBids: _callBids + }; +}; From 2a5b1228c2ee1f6fbf29b28fc2c83494d45cb4a4 Mon Sep 17 00:00:00 2001 From: Kevin Jennison Date: Mon, 6 Jun 2016 11:13:21 -0700 Subject: [PATCH 156/160] Bug Fix: restore `alwaysUseBid` functionality (#389) * Add test for `getAdserverTargeting`. * Add test for `getBidLandscapeTargeting`. * Move winning bid targeting result to fixtures. * Add test for ad server targeting when bid landscape reporting is on. * Add function to get targeting for bids with `alwaysUseBid` set to `true`. * Fix unsetting `enableSendAllBids`. * Update ad server targeting to include custom keys of `alwaysUseBid` bids. * Move `getAdserverTargeting` tests to test against public API. * Move `alwaysUseBid` targeting test to module level. * Add checks for ad server targeting keys. * Remove unneeded exports. * Remove public function to disable sendAllBids. * style updates (@protonate) --- src/prebid.js | 43 ++++++-- test/fixtures/fixtures.js | 174 ++++++++++++++++++++++++++++++++ test/spec/unit/pbjs_api_spec.js | 158 ++++++++++++++++++++++++++++- 3 files changed, 361 insertions(+), 14 deletions(-) diff --git a/src/prebid.js b/src/prebid.js index 7f1e5b32acb..fcec8e5ad83 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -25,7 +25,6 @@ var BID_WON = CONSTANTS.EVENTS.BID_WON; var BID_TIMEOUT = CONSTANTS.EVENTS.BID_TIMEOUT; var pb_bidsTimedOut = false; -var pb_sendAllBids = false; var auctionRunning = false; var presetTargeting = []; @@ -38,6 +37,7 @@ var eventValidators = { pbjs._bidsRequested = []; pbjs._bidsReceived = []; pbjs._adsReceived = []; +pbjs._sendAllBids = false; //default timeout for all bids pbjs.bidderTimeout = pbjs.bidderTimeout || 2000; @@ -177,7 +177,7 @@ function getWinningBidTargeting() { } function getDealTargeting() { - const dealTargeting = pbjs._bidsReceived.filter(bid => bid.dealId).map(bid => { + return pbjs._bidsReceived.filter(bid => bid.dealId).map(bid => { const dealKey = `hb_deal_${bid.bidderCode}`; return { [bid.adUnitCode]: CONSTANTS.TARGETING_KEYS.map(key => { @@ -188,8 +188,29 @@ function getDealTargeting() { .concat({ [dealKey]: [bid.adserverTargeting[dealKey]] }) }; }); +} - return dealTargeting; +/** + * Get custom targeting keys for bids that have `alwaysUseBid=true`. + */ +function getAlwaysUseBidTargeting() { + return pbjs._bidsReceived.map(bid => { + if (bid.alwaysUseBid) { + const standardKeys = CONSTANTS.TARGETING_KEYS; + return { + [bid.adUnitCode]: Object.keys(bid.adserverTargeting, key => key).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() { @@ -209,10 +230,12 @@ function getBidLandscapeTargeting() { } function getAllTargeting() { - let targeting = getWinningBidTargeting(); - // deals are always attached to targeting - targeting = getDealTargeting().concat(targeting); - return targeting.concat(pb_sendAllBids ? getBidLandscapeTargeting() : []); + // 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. + return getDealTargeting() + .concat(getWinningBidTargeting()) + .concat(getAlwaysUseBidTargeting()) + .concat(pbjs._sendAllBids ? getBidLandscapeTargeting() : []); } ////////////////////////////////// @@ -240,7 +263,7 @@ pbjs.getAdserverTargetingForAdUnitCodeStr = function (adunitCode) { }; /** -* This function returns the query string targeting parameters available at this moment for a given ad unit. Note that some bidder's response may not have been received if you call this function too quickly after the requests are sent. + * This function returns the query string targeting parameters available at this moment for a given ad unit. Note that some bidder's response may not have been received if you call this function too quickly after the requests are sent. * @param adUnitCode {string} adUnitCode to get the bid responses for * @returns {object} returnObj return bids */ @@ -423,7 +446,7 @@ pbjs.removeAdUnit = function (adUnitCode) { } }; -pbjs.clearAuction = function() { +pbjs.clearAuction = function () { auctionRunning = false; utils.logMessage('Prebid auction cleared'); }; @@ -676,7 +699,7 @@ pbjs.setPriceGranularity = function (granularity) { }; pbjs.enableSendAllBids = function () { - pb_sendAllBids = true; + pbjs._sendAllBids = true; }; processQue(); diff --git a/test/fixtures/fixtures.js b/test/fixtures/fixtures.js index 3799eab7208..5b6c95b20a7 100644 --- a/test/fixtures/fixtures.js +++ b/test/fixtures/fixtures.js @@ -1053,6 +1053,7 @@ export function getAdUnits() { ] }; +// Ad server targeting when `pbjs.enableSendAllBids()` is called. export function getAdServerTargeting() { return { "/19968336/header-bid-tag-0": { @@ -1103,3 +1104,176 @@ export function getAdServerTargeting() { } }; } + +// Key/values used to set ad server targeting. +export function getTargetingKeys() { + return [ + [ + "hb_bidder", + "appnexus" + ], + [ + "hb_adid", + "233bcbee889d46d" + ], + [ + "hb_pb", + "10.00" + ], + [ + "hb_size", + "300x250" + ], + [ + "foobar", + "300x250" + ], + [ + "foobar", + "300x250" + ] + ]; +} + +// Key/values used to set ad server targeting when bid landscape +// targeting is on. +export function getTargetingKeysBidLandscape() { + return [ + [ + "hb_bidder", + "appnexus" + ], + [ + "hb_adid", + "233bcbee889d46d" + ], + [ + "hb_pb", + "10.00" + ], + [ + "hb_size", + "300x250" + ], + [ + "foobar", + "300x250" + ], + [ + "foobar", + "300x250" + ], + [ + "hb_bidder_triplelift", + "triplelift" + ], + [ + "hb_adid_triplelift", + "222bb26f9e8bd" + ], + [ + "hb_pb_triplelift", + "10.00" + ], + [ + "hb_size_triplelift", + "0x0" + ], + [ + "hb_bidder_appnexus", + "appnexus" + ], + [ + "hb_adid_appnexus", + "233bcbee889d46d" + ], + [ + "hb_pb_appnexus", + "10.00" + ], + [ + "hb_size_appnexus", + "300x250" + ], + [ + "hb_bidder_pagescienc", + "pagescience" + ], + [ + "hb_adid_pagescience", + "25bedd4813632d7" + ], + [ + "hb_pb_pagescience", + "10.00" + ], + [ + "hb_size_pagescience", + "300x250" + ], + [ + "hb_bidder_brightcom", + "brightcom" + ], + [ + "hb_adid_brightcom", + "26e0795ab963896" + ], + [ + "hb_pb_brightcom", + "10.00" + ], + [ + "hb_size_brightcom", + "300x250" + ], + [ + "hb_bidder_brealtime", + "brealtime" + ], + [ + "hb_adid_brealtime", + "275bd666f5a5a5d" + ], + [ + "hb_pb_brealtime", + "10.00" + ], + [ + "hb_size_brealtime", + "300x250" + ], + [ + "hb_bidder_pubmatic", + "pubmatic" + ], + [ + "hb_adid_pubmatic", + "28f4039c636b6a7" + ], + [ + "hb_pb_pubmatic", + "10.00" + ], + [ + "hb_size_pubmatic", + "300x250" + ], + [ + "hb_bidder_rubicon", + "rubicon" + ], + [ + "hb_adid_rubicon", + "29019e2ab586a5a" + ], + [ + "hb_pb_rubicon", + "10.00" + ], + [ + "hb_size_rubicon", + "300x600" + ] + ]; +} diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index 68ecc092200..816ea43924c 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -1,4 +1,10 @@ -import { getBidRequests, getBidResponses, getAdServerTargeting } from 'test/fixtures/fixtures'; +import { + getAdServerTargeting, + getBidRequests, + getBidResponses, + getTargetingKeys, + getTargetingKeysBidLandscape, +} from 'test/fixtures/fixtures'; var assert = require('chai').assert; @@ -20,6 +26,7 @@ pbjs._bidsRequested = getBidRequests(); pbjs._bidsReceived = getBidResponses(); function resetAuction() { + pbjs._sendAllBids = false; pbjs.clearAuction(); pbjs._bidsRequested = getBidRequests(); pbjs._bidsReceived = getBidResponses(); @@ -110,12 +117,90 @@ describe('Unit: Prebid Module', function () { }); describe('getAdServerTargeting', function () { + + beforeEach(() => { + resetAuction(); + }); + + afterEach(() => { + resetAuction(); + }); + it('should return current targeting data for slots', function () { + pbjs.enableSendAllBids(); const targeting = pbjs.getAdserverTargeting(); const expected = getAdServerTargeting(); - pbjs.enableSendAllBids(); assert.deepEqual(targeting, expected, 'targeting ok'); }); + + it('should return correct targeting with default settings', () => { + var targeting = pbjs.getAdserverTargeting(); + var expected = { + "/19968336/header-bid-tag-0": { + "foobar": "300x250", + "hb_size": "300x250", + "hb_pb": "10.00", + "hb_adid": "233bcbee889d46d", + "hb_bidder": "appnexus" + }, + "/19968336/header-bid-tag1": { + "foobar": "728x90", + "hb_size": "728x90", + "hb_pb": "10.00", + "hb_adid": "24bd938435ec3fc", + "hb_bidder": "appnexus" + } + }; + assert.deepEqual(targeting, expected); + }); + + it('should return correct targeting with bid landscape targeting on', () => { + pbjs.enableSendAllBids(); + var targeting = pbjs.getAdserverTargeting(); + var expected = getAdServerTargeting(); + assert.deepEqual(targeting, expected); + }); + + it("should include a losing bid's custom ad targeting key when the bid has `alwaysUseBid` set to `true`", () => { + + // Let's make sure we're getting the expected losing bid. + assert.equal(pbjs._bidsReceived[0]['bidderCode'], 'triplelift'); + assert.equal(pbjs._bidsReceived[0]['cpm'], 0.112256); + + // Modify the losing bid to have `alwaysUseBid=true` and a custom `adserverTargeting` key. + pbjs._bidsReceived[0]['alwaysUseBid'] = true; + pbjs._bidsReceived[0]['adserverTargeting'] = { + 'always_use_me': 'abc', + }; + + var targeting = pbjs.getAdserverTargeting(); + + // Ensure targeting for both ad placements includes the custom key. + assert.equal( + targeting['/19968336/header-bid-tag-0'].hasOwnProperty('always_use_me'), + true + ); + + var expected = { + "/19968336/header-bid-tag-0": { + "foobar": "300x250", + "hb_size": "300x250", + "hb_pb": "10.00", + "hb_adid": "233bcbee889d46d", + "hb_bidder": "appnexus", + "always_use_me": "abc" + }, + "/19968336/header-bid-tag1": { + "foobar": "728x90", + "hb_size": "728x90", + "hb_pb": "10.00", + "hb_adid": "24bd938435ec3fc", + "hb_bidder": "appnexus" + } + }; + + assert.deepEqual(targeting, expected); + }); }); describe('getBidResponses', function () { @@ -147,8 +232,16 @@ describe('Unit: Prebid Module', function () { describe('setTargetingForGPTAsync', function () { let logErrorSpy; - beforeEach(() => logErrorSpy = sinon.spy(utils, 'logError')); - afterEach(() => utils.logError.restore()); + + beforeEach(() => { + logErrorSpy = sinon.spy(utils, 'logError'); + resetAuction(); + }); + + afterEach(() => { + utils.logError.restore(); + resetAuction(); + }); it('should set targeting when passed an array of ad unit codes', function () { var slots = createSlotArray(); @@ -163,6 +256,9 @@ describe('Unit: Prebid Module', function () { window.googletag.pubads().setSlots(slots); pbjs.setTargetingForGPTAsync(); + + var expected = getTargetingKeys(); + assert.deepEqual(slots[0].spySetTargeting.args, expected); }); it('Calling enableSendAllBids should set targeting to include standard keys with bidder' + @@ -172,6 +268,60 @@ describe('Unit: Prebid Module', function () { pbjs.enableSendAllBids(); pbjs.setTargetingForGPTAsync(); + + var expected = getTargetingKeysBidLandscape(); + assert.deepEqual(slots[0].spySetTargeting.args, expected); + }); + + it('should set targeting for bids with `alwaysUseBid=true`', function () { + + // Make sure we're getting the expected losing bid. + assert.equal(pbjs._bidsReceived[0]['bidderCode'], 'triplelift'); + assert.equal(pbjs._bidsReceived[0]['cpm'], 0.112256); + + // Modify the losing bid to have `alwaysUseBid=true` and a custom `adserverTargeting` key. + pbjs._bidsReceived[0]['alwaysUseBid'] = true; + pbjs._bidsReceived[0]['adserverTargeting'] = { + 'always_use_me': 'abc', + }; + + var slots = createSlotArray(); + window.googletag.pubads().setSlots(slots); + + pbjs.setTargetingForGPTAsync(config.adUnitCodes); + + var expected = [ + [ + "hb_bidder", + "appnexus" + ], + [ + "hb_adid", + "233bcbee889d46d" + ], + [ + "hb_pb", + "10.00" + ], + [ + "hb_size", + "300x250" + ], + [ + "foobar", + "300x250" + ], + [ + "always_use_me", + "abc" + ], + [ + "foobar", + "300x250" + ] + ]; + + assert.deepEqual(slots[0].spySetTargeting.args, expected); }); it('should log error when googletag is not defined on page', function () { From 7b53fce8710e59f6fd588630b45c23a5e4032fc1 Mon Sep 17 00:00:00 2001 From: devmusings Date: Wed, 15 Jun 2016 15:26:08 -0700 Subject: [PATCH 157/160] Admedia header bidding adapter (#405) --- README.md | 1 + integrationExamples/gpt/pbjs_example_gpt.html | 10 +- package.json | 1 + src/adapters/admedia.js | 108 ++++++++++++++++++ 4 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 src/adapters/admedia.js diff --git a/README.md b/README.md index eff6d81a28e..6eec8a44980 100644 --- a/README.md +++ b/README.md @@ -152,6 +152,7 @@ Edit example file `./integrationExamples/gpt/pbjs_example_gpt.html`: ```json "adapters": [ "adform", + "admedia", "aol", "appnexus", "indexExchange", diff --git a/integrationExamples/gpt/pbjs_example_gpt.html b/integrationExamples/gpt/pbjs_example_gpt.html index 477f5319a1b..2a8bbb1c150 100644 --- a/integrationExamples/gpt/pbjs_example_gpt.html +++ b/integrationExamples/gpt/pbjs_example_gpt.html @@ -88,7 +88,15 @@ //The RTK Ad Unit ID (shortcode) sc: "TO ADD" } - }, { + }, + { + bidder:'admedia', + params: { + //Publisher ID + aid: '1234' //Use 1234 for test ads + } + }, + { bidder: 'pubmatic', params: { publisherId: 'TO ADD', diff --git a/package.json b/package.json index 19395e42a25..f114be9d16a 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "aardvark", "adequant", "adform", + "admedia", "aol", "appnexus", "indexExchange", diff --git a/src/adapters/admedia.js b/src/adapters/admedia.js new file mode 100644 index 00000000000..b62633e807c --- /dev/null +++ b/src/adapters/admedia.js @@ -0,0 +1,108 @@ +import { getBidRequest } from '../utils.js'; +var bidfactory = require('../bidfactory.js'); +var bidmanager = require('../bidmanager.js'); +var adloader = require('../adloader.js'); +var utils = require('../utils.js'); +var CONSTANTS = require('../constants.json'); + +/** + * Adapter for requesting bids from AdMedia. + * + */ +var AdmediaAdapter = function AdmediaAdapter() { + + function _callBids(params){ + var bids, bidderUrl = (window.location.protocol) + "//b.admedia.com/banner/prebid/bidder/?"; + bids = params.bids || []; + for (var i = 0; i < bids.length; i++) { + var request_obj = {}; + var bid = bids[i]; + + if (bid.params.aid) { + request_obj.aid = bid.params.aid; + } + else{ + utils.logError('required param aid is missing', "admedia"); + continue; + } + + //optional page_url macro + if (bid.params.page_url) { + request_obj.page_url = bid.params.page_url; + } + + //if set, return a test ad for all aids + if (bid.params.test_ad === 1) { + request_obj.test_ad = 1; + } + + var parsedSizes = utils.parseSizesInput(bid.sizes); + var parsedSizesLength = parsedSizes.length; + if (parsedSizesLength > 0) { + //first value should be "size" + request_obj.size = parsedSizes[0]; + if (parsedSizesLength > 1) { + //any subsequent values should be "promo_sizes" + var promo_sizes = []; + for (var j = 1; j < parsedSizesLength; j++) { + promo_sizes.push(parsedSizes[j]); + } + + request_obj.promo_sizes = promo_sizes.join(","); + + } + } + + //detect urls + request_obj.siteDomain = window.location.host; + request_obj.sitePage = window.location.href; + request_obj.siteRef = document.referrer; + request_obj.topUrl = utils.getTopWindowUrl(); + + request_obj.callbackId = bid.bidId; + + var endpoint = bidderUrl+utils.parseQueryStringParameters(request_obj); + + //utils.logMessage('Admedia request built: ' + endpoint); + + adloader.loadScript(endpoint); + } + } + + //expose the callback to global object + pbjs.admediaHandler = function(response){ + var bidObject = {}; + var callback_id = response.callback_id; + var placementCode = ''; + var bidObj = getBidRequest(callback_id); + if (bidObj) { + placementCode = bidObj.placementCode; + } + + if(bidObj && response.cpm>0 && !!response.ad){ + bidObject = bidfactory.createBid(CONSTANTS.STATUS.GOOD); + bidObject.bidderCode = bidObj.bidder; + bidObject.cpm = parseFloat(response.cpm); + bidObject.ad = response.ad; + bidObject.width = response.width; + bidObject.height = response.height; + } + else{ + bidObject = bidfactory.createBid(CONSTANTS.STATUS.NO_BID); + bidObject.bidderCode = bidObj.bidder; + utils.logMessage('No prebid response from Admedia for placement code ' + placementCode); + } + + bidmanager.addBidResponse(placementCode, bidObject); + + }; + + + // 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 = AdmediaAdapter; \ No newline at end of file From dc34f6cb32737caeb7f49d16f42bd5513e67d0e2 Mon Sep 17 00:00:00 2001 From: Nate Guisinger Date: Thu, 16 Jun 2016 06:57:30 -0700 Subject: [PATCH 158/160] configure browserstack local for karma tests (#408) --- gulpfile.js | 31 +++++++ karma.conf.js | 219 ++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 3 +- 3 files changed, 252 insertions(+), 1 deletion(-) diff --git a/gulpfile.js b/gulpfile.js index f8c58f14340..9177123e0da 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -81,6 +81,37 @@ gulp.task('test', function () { var defaultBrowsers = CI_MODE ? ['PhantomJS'] : ['Chrome']; var browserArgs = helpers.parseBrowserArgs(argv).map(helpers.toCapitalCase); + if (argv.browserstack) { + browserArgs = [ + 'bs_ie_13_windows_10', + 'bs_ie_12_windows_10', + 'bs_ie_11_windows_10', + 'bs_firefox_46_windows_10', + 'bs_chrome_51_windows_10', + 'bs_ie_11_windows_8.1', + 'bs_firefox_46_windows_8.1', + 'bs_chrome_51_windows_8.1', + 'bs_ie_10_windows_8', + 'bs_firefox_46_windows_8', + 'bs_chrome_51_windows_8', + 'bs_ie_11_windows_7', + 'bs_ie_10_windows_7', + 'bs_ie_9_windows_7', + 'bs_firefox_46_windows_7', + 'bs_chrome_51_windows_7', + 'bs_safari_9.1_mac_elcapitan', + 'bs_firefox_46_mac_elcapitan', + 'bs_chrome_51_mac_elcapitan', + 'bs_safari_8_mac_yosemite', + 'bs_firefox_46_mac_yosemite', + 'bs_chrome_51_mac_yosemite', + 'bs_safari_7.1_mac_mavericks', + 'bs_safari_6.2_mac_mavericks', + 'bs_firefox_46_mac_mavericks', + 'bs_chrome_49_mac_mavericks' + ]; + } + return gulp.src('lookAtKarmaConfJS') .pipe(karma({ browsers: (browserArgs.length > 0) ? browserArgs : defaultBrowsers, diff --git a/karma.conf.js b/karma.conf.js index 1f910b70225..ee00f834eca 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -17,6 +17,224 @@ module.exports = function (config) { // base path that will be used to resolve all patterns (eg. files, exclude) basePath: './', + // BrowserStack Config + browserStack: { + username: process.env.BROWSERSTACK_USERNAME, + accessKey: process.env.BROWSERSTACK_KEY + }, + + // define browsers + customLaunchers: { + bs_ie_13_windows_10: { + base: 'BrowserStack', + os_version: '10', + browser: 'edge', + browser_version: '13.0', + device: null, + os: 'Windows' + }, + bs_ie_12_windows_10: { + base: 'BrowserStack', + os_version: '10', + browser: 'ie', + browser_version: '12.0', + device: null, + os: 'Windows' + }, + bs_ie_11_windows_10: { + base: 'BrowserStack', + os_version: '10', + browser: 'ie', + browser_version: '11.0', + device: null, + os: 'Windows' + }, + bs_firefox_46_windows_10: { + base: 'BrowserStack', + os_version: '10', + browser: 'firefox', + browser_version: '46.0', + device: null, + os: 'Windows' + }, + bs_chrome_51_windows_10: { + base: 'BrowserStack', + os_version: '10', + browser: 'chrome', + browser_version: '51.0', + device: null, + os: 'Windows' + }, + 'bs_ie_11_windows_8.1': { + base: 'BrowserStack', + os_version: '8.1', + browser: 'ie', + browser_version: '11.0', + device: null, + os: 'Windows' + }, + 'bs_firefox_46_windows_8.1': { + base: 'BrowserStack', + os_version: '8.1', + browser: 'firefox', + browser_version: '46.0', + device: null, + os: 'Windows' + }, + 'bs_chrome_51_windows_8.1': { + base: 'BrowserStack', + os_version: '8.1', + browser: 'chrome', + browser_version: '51.0', + device: null, + os: 'Windows' + }, + bs_ie_10_windows_8: { + base: 'BrowserStack', + os_version: '8', + browser: 'ie', + browser_version: '10.0', + device: null, + os: 'Windows' + }, + bs_firefox_46_windows_8: { + base: 'BrowserStack', + os_version: '8', + browser: 'firefox', + browser_version: '46.0', + device: null, + os: 'Windows' + }, + bs_chrome_51_windows_8: { + base: 'BrowserStack', + os_version: '8', + browser: 'chrome', + browser_version: '51.0', + device: null, + os: 'Windows' + }, + bs_ie_11_windows_7: { + base: 'BrowserStack', + os_version: '7', + browser: 'ie', + browser_version: '11.0', + device: null, + os: 'Windows' + }, + bs_ie_10_windows_7: { + base: 'BrowserStack', + os_version: '7', + browser: 'ie', + browser_version: '10.0', + device: null, + os: 'Windows' + }, + bs_ie_9_windows_7: { + base: 'BrowserStack', + os_version: '7', + browser: 'ie', + browser_version: '9.0', + device: null, + os: 'Windows' + }, + bs_firefox_46_windows_7: { + base: 'BrowserStack', + os_version: '7', + browser: 'firefox', + browser_version: '46.0', + device: null, + os: 'Windows' + }, + bs_chrome_51_windows_7: { + base: 'BrowserStack', + os_version: '7', + browser: 'chrome', + browser_version: '51.0', + device: null, + os: 'Windows' + }, + 'bs_safari_9.1_mac_elcapitan': { + base: 'BrowserStack', + os_version: 'El Capitan', + browser: 'safari', + browser_version: '9.1', + device: null, + os: 'OS X' + }, + bs_firefox_46_mac_elcapitan: { + base: 'BrowserStack', + os_version: 'El Capitan', + browser: 'firefox', + browser_version: '46.0', + device: null, + os: 'OS X' + }, + bs_chrome_51_mac_elcapitan: { + base: 'BrowserStack', + os_version: 'El Capitan', + browser: 'chrome', + browser_version: '51.0', + device: null, + os: 'OS X' + }, + bs_safari_8_mac_yosemite: { + base: 'BrowserStack', + os_version: 'Yosemite', + browser: 'safari', + browser_version: '8.0', + device: null, + os: 'OS X' + }, + bs_firefox_46_mac_yosemite: { + base: 'BrowserStack', + os_version: 'Yosemite', + browser: 'firefox', + browser_version: '46.0', + device: null, + os: 'OS X' + }, + bs_chrome_51_mac_yosemite: { + base: 'BrowserStack', + os_version: 'Yosemite', + browser: 'chrome', + browser_version: '51.0', + device: null, + os: 'OS X' + }, + 'bs_safari_7.1_mac_mavericks': { + base: 'BrowserStack', + os_version: 'Mavericks', + browser: 'safari', + browser_version: '7.1', + device: null, + os: 'OS X' + }, + 'bs_safari_6.2_mac_mavericks': { + base: 'BrowserStack', + os_version: 'Mavericks', + browser: 'safari', + browser_version: '6.2', + device: null, + os: 'OS X' + }, + bs_firefox_46_mac_mavericks: { + base: 'BrowserStack', + os_version: 'Mavericks', + browser: 'firefox', + browser_version: '46.0', + device: null, + os: 'OS X' + }, + bs_chrome_49_mac_mavericks: { + base: 'BrowserStack', + os_version: 'Mavericks', + browser: 'chrome', + browser_version: '49.0', + device: null, + os: 'OS X' + } + }, + // frameworks to use // available frameworks: https://npmjs.org/browse/keyword/karma-adapter frameworks: ['es5-shim', 'mocha', 'expect', 'sinon'], @@ -90,6 +308,7 @@ module.exports = function (config) { singleRun: false, plugins: [ + 'karma-browserstack-launcher', 'karma-phantomjs-launcher', 'karma-nyan-reporter', 'karma-coverage', diff --git a/package.json b/package.json index f114be9d16a..af06e51ea86 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "yieldbot", "nginad", "brightcom", - "wideorbit", + "wideorbit", { "appnexus": { "alias": "brealtime" @@ -81,6 +81,7 @@ "json-loader": "^0.5.1", "karma": "^0.13.2", "karma-babel-preprocessor": "^6.0.1", + "karma-browserstack-launcher": "^1.0.1", "karma-chai": "^0.1.0", "karma-chrome-launcher": "^0.2.0", "karma-coverage": "^0.2.6", From 08f5bc40881be412c081e8ade4890f10e793e355 Mon Sep 17 00:00:00 2001 From: Benjamin Clot Date: Thu, 16 Jun 2016 16:36:47 +0200 Subject: [PATCH 159/160] Add reserve parameter + remove duplicate (#413) remove duplicate line in AppNexus adapter --- src/adapters/appnexus.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/adapters/appnexus.js b/src/adapters/appnexus.js index 23b37f86840..00d2c7082e6 100644 --- a/src/adapters/appnexus.js +++ b/src/adapters/appnexus.js @@ -59,7 +59,6 @@ AppNexusAdapter = function AppNexusAdapter() { utils.logMessage('appnexus.callBids: "memberId" will be deprecated soon. Please use "member" instead'); } - jptCall = utils.tryAppendQueryString(jptCall, 'code', inventoryCode); jptCall = utils.tryAppendQueryString(jptCall, 'code', inventoryCode); //sizes takes a bit more logic From 1bc8f32753beef53c3650c9aadfd39d98e9c9e75 Mon Sep 17 00:00:00 2001 From: Alexander Kozlov Date: Mon, 20 Jun 2016 15:03:45 +0300 Subject: [PATCH 160/160] Code review. --- src/adapters/adform.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/adapters/adform.js b/src/adapters/adform.js index c89a20b7592..479dfd03824 100644 --- a/src/adapters/adform.js +++ b/src/adapters/adform.js @@ -117,7 +117,7 @@ function AdformAdapter() { var out = []; var chr1, chr2, chr3, enc1, enc2, enc3, enc4; var i = 0; - var _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_="; + var _keyStr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_='; input = utf8_encode(input); @@ -138,9 +138,9 @@ function AdformAdapter() { enc4 = 64; } out.push(_keyStr.charAt(enc1), _keyStr.charAt(enc2)); - if (enc3 != 64) + if (enc3 !== 64) out.push(_keyStr.charAt(enc3)); - if (enc4 != 64) + if (enc4 !== 64) out.push(_keyStr.charAt(enc4)); } @@ -148,8 +148,8 @@ function AdformAdapter() { } function utf8_encode(string) { - string = string.replace(/\r\n/g, "\n"); - var utftext = ""; + string = string.replace(/\r\n/g, '\n'); + var utftext = ''; for (var n = 0; n < string.length; n++) {