diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js
index 1517a53eee1..8176052f9e2 100644
--- a/modules/pubmaticBidAdapter.js
+++ b/modules/pubmaticBidAdapter.js
@@ -2,6 +2,7 @@ var utils = require('src/utils.js');
var bidfactory = require('src/bidfactory.js');
var bidmanager = require('src/bidmanager.js');
var adaptermanager = require('src/adaptermanager');
+const constants = require('src/constants.json');
/**
* Adapter for requesting bids from Pubmatic.
@@ -9,130 +10,208 @@ var adaptermanager = require('src/adaptermanager');
* @returns {{callBids: _callBids}}
* @constructor
*/
-function PubmaticAdapter() {
- var bids;
- var _pm_pub_id;
- var _pm_pub_age;
- var _pm_pub_gender;
- var _pm_pub_kvs;
- var _pm_optimize_adslots = [];
+const PubmaticAdapter = function PubmaticAdapter() {
+ let bids;
+ let usersync = false;
+ let _secure = 0;
+ let _protocol = (window.location.protocol === 'https:' ? (_secure = 1, 'https') : 'http') + '://';
let iframe;
- function _callBids(params) {
- bids = params.bids;
- _pm_optimize_adslots = [];
- 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_pub_age = _pm_pub_age || (bid.params.age || '');
- _pm_pub_gender = _pm_pub_gender || (bid.params.gender || '');
- _pm_pub_kvs = _pm_pub_kvs || (bid.params.kvs || '');
- _pm_optimize_adslots.push(bid.params.adSlot);
+ let dealChannelValues = {
+ 1: 'PMP',
+ 5: 'PREF',
+ 6: 'PMPG'
+ };
+
+ let customPars = {
+ 'kadgender': 'gender',
+ 'age': 'kadage',
+ 'dctr': 'dctr', // Custom Targeting
+ 'wiid': 'wiid', // Wrapper Impression ID
+ 'profId': 'profId', // Legacy: Profile ID
+ 'verId': 'verId', // Legacy: version ID
+ 'pmzoneid': { // Zone ID
+ n: 'pmZoneId',
+ m: function(zoneId) {
+ if (utils.isStr(zoneId)) {
+ return zoneId.split(',').slice(0, 50).join();
+ } else {
+ return undefined;
+ }
+ }
+ }
+ };
+
+ function _initConf() {
+ var conf = {};
+ var currTime = new Date();
+
+ conf.SAVersion = '1100';
+ conf.wp = 'PreBid';
+ conf.js = 1;
+ conf.wv = constants.REPO_AND_VERSION;
+ _secure && (conf.sec = 1);
+ conf.screenResolution = screen.width + 'x' + screen.height;
+ conf.ranreq = Math.random();
+ conf.inIframe = window != top ? '1' : '0';
+
+ // istanbul ignore else
+ if (window.navigator.cookieEnabled === false) {
+ conf.fpcd = '1';
}
- // Load pubmatic script in an iframe, because they call document.write
- _getBids();
+ try {
+ conf.pageURL = window.top.location.href;
+ conf.refurl = window.top.document.referrer;
+ } catch (e) {
+ conf.pageURL = window.location.href;
+ conf.refurl = window.document.referrer;
+ }
+
+ conf.kltstamp = currTime.getFullYear() +
+ '-' + (currTime.getMonth() + 1) +
+ '-' + currTime.getDate() +
+ ' ' + currTime.getHours() +
+ ':' + currTime.getMinutes() +
+ ':' + currTime.getSeconds();
+ conf.timezone = currTime.getTimezoneOffset() / 60 * -1;
+
+ return conf;
}
- function _getBids() {
- // create the iframe
- iframe = utils.createInvisibleIframe();
+ function _handleCustomParams(params, conf) {
+ // istanbul ignore else
+ if (!conf.kadpageurl) {
+ conf.kadpageurl = conf.pageURL;
+ }
- var elToAppend = document.getElementsByTagName('head')[0];
+ var key, value, entry;
+ for (key in customPars) {
+ // istanbul ignore else
+ if (customPars.hasOwnProperty(key)) {
+ value = params[key];
+ // istanbul ignore else
+ if (value) {
+ entry = customPars[key];
+
+ if (typeof entry === 'object') {
+ value = entry.m(value, conf);
+ key = entry.n;
+ } else {
+ key = customPars[key];
+ }
+
+ if (utils.isStr(value)) {
+ conf[key] = value;
+ } else {
+ utils.logWarn('PubMatic: Ignoring param key: ' + customPars[key] + ', expects string-value, found ' + typeof value);
+ }
+ }
+ }
+ }
+ return conf;
+ }
- // insert the iframe into document
- elToAppend.insertBefore(iframe, elToAppend.firstChild);
+ function _cleanSlot(slotName) {
+ // istanbul ignore else
+ if (utils.isStr(slotName)) {
+ return slotName.replace(/^\s+/g, '').replace(/\s+$/g, '');
+ }
+ return '';
+ }
+ function _legacyExecution(conf, slots) {
+ var url = _generateLegacyCall(conf, slots);
+ iframe = utils.createInvisibleIframe();
+ var elToAppend = document.getElementsByTagName('head')[0];
+ elToAppend.insertBefore(iframe, elToAppend.firstChild);
var iframeDoc = utils.getIframeDocument(iframe);
- iframeDoc.write(_createRequestContent());
+ var content = utils.createContentToExecuteExtScriptInFriendlyFrame(url);
+ content = content.replace(``, ``);
+ iframeDoc.write(content);
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%%];' +
- 'window.kaddctr = "%%PM_ADDCTR%%";' +
- 'window.kadgender = "%%PM_GENDER%%";' +
- 'window.kadage = "%%PM_AGE%%";' +
- 'window.pm_async_callback_fn = "window.parent.$$PREBID_GLOBAL$$.handlePubmaticCallback";';
-
- content += '';
-
- var map = {};
- map.PM_PUB_ID = _pm_pub_id;
- map.PM_ADDCTR = _pm_pub_kvs;
- map.PM_GENDER = _pm_pub_gender;
- map.PM_AGE = _pm_pub_age;
- map.PM_OPTIMIZE_ADSLOTS = _pm_optimize_adslots.map(function (adSlot) {
- return "'" + adSlot + "'";
- }).join(',');
-
- content += '';
- content += '';
- content += '';
- content += '';
- content = utils.replaceTokenInString(content, map, '%%');
-
- return content;
+ function _generateLegacyCall(conf, slots) {
+ var request_url = 'gads.pubmatic.com/AdServer/AdCallAggregator';
+ return _protocol + request_url + '?' + utils.parseQueryStringParameters(conf) + 'adslots=' + encodeURIComponent('[' + slots.join(',') + ']');
}
- $$PREBID_GLOBAL$$.handlePubmaticCallback = function () {
- let bidDetailsMap = {};
- let progKeyValueMap = {};
- try {
- bidDetailsMap = iframe.contentWindow.bidDetailsMap;
- progKeyValueMap = iframe.contentWindow.progKeyValueMap;
- } catch (e) {
- utils.logError(e, 'Error parsing Pubmatic response');
+ function _initUserSync(pubId) {
+ // istanbul ignore else
+ if (!usersync) {
+ var iframe = utils.createInvisibleIframe();
+ iframe.src = _protocol + 'ads.pubmatic.com/AdServer/js/showad.js#PIX&kdntuid=1&p=' + pubId;
+ utils.insertElement(iframe, document);
+ usersync = true;
+ }
+ }
+
+ function _callBids(params) {
+ var conf = _initConf();
+ var slots = [];
+
+ conf.pubId = 0;
+ bids = params.bids || [];
+
+ for (var i = 0; i < bids.length; i++) {
+ var bid = bids[i];
+ conf.pubId = conf.pubId || bid.params.publisherId;
+ conf = _handleCustomParams(bid.params, conf);
+ bid.params.adSlot = _cleanSlot(bid.params.adSlot);
+ bid.params.adSlot.length && slots.push(bid.params.adSlot);
+ }
+
+ // istanbul ignore else
+ if (conf.pubId && slots.length > 0) {
+ _legacyExecution(conf, slots);
}
+ _initUserSync(conf.pubId);
+ }
+
+ $$PREBID_GLOBAL$$.handlePubmaticCallback = function(bidDetailsMap, progKeyValueMap) {
var i;
var adUnit;
var adUnitInfo;
var bid;
- var bidResponseMap = bidDetailsMap || {};
- var bidInfoMap = progKeyValueMap || {};
- var dimensions;
+ var bidResponseMap = bidDetailsMap;
+ var bidInfoMap = progKeyValueMap;
+
+ if (!bidResponseMap || !bidInfoMap) {
+ return;
+ }
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=
-
// if using DFP GPT, the params string comes in the format:
// "bidstatus;1;bid;5.0000;bidid;hb_test@468x60;wdeal;"
// the code below detects and handles this.
+ // istanbul ignore else
if (bidInfoMap[bid.adSlot] && bidInfoMap[bid.adSlot].indexOf('=') === -1) {
bidInfoMap[bid.adSlot] = bidInfoMap[bid.adSlot].replace(/([a-z]+);(.[^;]*)/ig, '$1=$2');
}
- adUnitInfo = (bidInfoMap[bid.adSlot] || '').split(';').reduce(function (result, pair) {
+ 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.ad += utils.createTrackPixelIframeHtml(decodeURIComponent(adUnit.tracking_url));
- adResponse.width = dimensions[0];
- adResponse.height = dimensions[1];
+ adResponse.width = adUnit.width;
+ adResponse.height = adUnit.height;
adResponse.dealId = adUnitInfo.wdeal;
+ adResponse.dealChannel = dealChannelValues[adUnit.deal_channel] || null;
bidmanager.addBidResponse(bids[i].placementCode, adResponse);
} else {
@@ -147,7 +226,7 @@ function PubmaticAdapter() {
return {
callBids: _callBids
};
-}
+};
adaptermanager.registerBidAdapter(new PubmaticAdapter(), 'pubmatic');
diff --git a/src/utils.js b/src/utils.js
index 9efa4f53c57..9e481c9aceb 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -730,6 +730,19 @@ export function deepAccess(obj, path) {
return obj;
}
+/**
+ * Returns content for a friendly iframe to execute a URL in script tag
+ * @param {url} URL to be executed in a script tag in a friendly iframe
+ * and are macros left to be replaced if required
+ */
+export function createContentToExecuteExtScriptInFriendlyFrame(url) {
+ if (!url) {
+ return '';
+ }
+
+ return ``;
+}
+
/**
* Build an object consisting of only defined parameters to avoid creating an
* object with defined keys and undefined values.
diff --git a/test/spec/modules/pubmaticBidAdapter_spec.js b/test/spec/modules/pubmaticBidAdapter_spec.js
new file mode 100644
index 00000000000..c7b8cd5cd8e
--- /dev/null
+++ b/test/spec/modules/pubmaticBidAdapter_spec.js
@@ -0,0 +1,276 @@
+import {
+ expect
+} from 'chai';
+import * as utils from 'src/utils';
+import PubMaticAdapter from 'modules/pubmaticBidAdapter';
+import bidmanager from 'src/bidmanager';
+import constants from 'src/constants.json';
+
+let getDefaultBidRequest = () => {
+ return {
+ bidderCode: 'pubmatic',
+ requestId: 'd3e07445-ab06-44c8-a9dd-5ef9af06d2a6',
+ bidderRequestId: '7101db09af0db2',
+ start: new Date().getTime(),
+ bids: [{
+ bidder: 'pubmatic',
+ bidId: '84ab500420319d',
+ bidderRequestId: '7101db09af0db2',
+ requestId: 'd3e07445-ab06-44c8-a9dd-5ef9af06d2a6',
+ placementCode: 'DIV_1',
+ params: {
+ placement: 1234567,
+ network: '9599.1'
+ }
+ }]
+ };
+};
+
+describe('PubMaticAdapter', () => {
+ let adapter;
+
+ function createBidderRequest({
+ bids,
+ params
+ } = {}) {
+ var bidderRequest = getDefaultBidRequest();
+ if (bids && Array.isArray(bids)) {
+ bidderRequest.bids = bids;
+ }
+ if (params) {
+ bidderRequest.bids.forEach(bid => bid.params = params);
+ }
+ return bidderRequest;
+ }
+
+ beforeEach(() => adapter = new PubMaticAdapter());
+
+ describe('callBids()', () => {
+ it('exists and is a function', () => {
+ expect(adapter.callBids).to.exist.and.to.be.a('function');
+ });
+
+ describe('user syncup', () => {
+ beforeEach(() => {
+ sinon.stub(utils, 'insertElement');
+ });
+
+ afterEach(() => {
+ utils.insertElement.restore();
+ });
+
+ it('usersync is initiated', () => {
+ adapter.callBids(createBidderRequest({
+ params: {
+ publisherId: 9999,
+ adSlot: 'abcd@728x90',
+ age: '20'
+ }
+ }));
+ utils.insertElement.calledOnce.should.be.true;
+ expect(utils.insertElement.getCall(0).args[0].src).to.equal('http://ads.pubmatic.com/AdServer/js/showad.js#PIX&kdntuid=1&p=9999');
+ });
+ });
+
+ describe('bid request', () => {
+ beforeEach(() => {
+ sinon.stub(utils, 'createContentToExecuteExtScriptInFriendlyFrame', function() {
+ return '';
+ });
+ });
+
+ afterEach(() => {
+ utils.createContentToExecuteExtScriptInFriendlyFrame.restore();
+ });
+
+ it('requires parameters to be made', () => {
+ adapter.callBids({});
+ utils.createContentToExecuteExtScriptInFriendlyFrame.calledOnce.should.be.false;
+ });
+
+ it('for publisherId 9990 call is made to gads.pubmatic.com', () => {
+ var bidRequest = createBidderRequest({
+ params: {
+ publisherId: 9990,
+ adSlot: ' abcd@728x90',
+ age: '20',
+ wiid: 'abcdefghijk',
+ profId: '1234',
+ verId: '12',
+ pmzoneid: 'abcd123, efg345',
+ dctr: 'key=1234,5678'
+ }
+ });
+ adapter.callBids(bidRequest);
+ var callURL = utils.createContentToExecuteExtScriptInFriendlyFrame.getCall(0).args[0];
+ expect(bidRequest.bids[0].params.adSlot).to.equal('abcd@728x90');
+ expect(callURL).to.contain('gads.pubmatic.com/AdServer/AdCallAggregator?');
+ expect(callURL).to.contain('SAVersion=1100');
+ expect(callURL).to.contain('wp=PreBid');
+ expect(callURL).to.contain('js=1');
+ expect(callURL).to.contain('screenResolution=');
+ expect(callURL).to.contain('wv=' + constants.REPO_AND_VERSION);
+ expect(callURL).to.contain('ranreq=');
+ expect(callURL).to.contain('inIframe=');
+ expect(callURL).to.contain('pageURL=');
+ expect(callURL).to.contain('refurl=');
+ expect(callURL).to.contain('kltstamp=');
+ expect(callURL).to.contain('timezone=');
+ expect(callURL).to.contain('age=20');
+ expect(callURL).to.contain('adslots=%5Babcd%40728x90%5D');
+ expect(callURL).to.contain('kadpageurl=');
+ expect(callURL).to.contain('wiid=abcdefghijk');
+ expect(callURL).to.contain('profId=1234');
+ expect(callURL).to.contain('verId=12');
+ expect(callURL).to.contain('pmZoneId=abcd123%2C%20efg345');
+ expect(callURL).to.contain('dctr=key%3D1234%2C5678');
+ });
+
+ it('for publisherId 9990 call is made to gads.pubmatic.com, age passed as int not being passed ahead', () => {
+ adapter.callBids(createBidderRequest({
+ params: {
+ publisherId: 9990,
+ adSlot: 'abcd@728x90',
+ age: 20,
+ wiid: 'abcdefghijk',
+ profId: '1234',
+ verId: '12',
+ pmzoneid: {},
+ dctr: 1234
+ }
+ }));
+ var callURL = utils.createContentToExecuteExtScriptInFriendlyFrame.getCall(0).args[0];
+ expect(callURL).to.contain('gads.pubmatic.com/AdServer/AdCallAggregator?');
+ expect(callURL).to.not.contain('age=20');
+ expect(callURL).to.not.contain('dctr=1234');
+ });
+
+ it('for publisherId 9990 call is made to gads.pubmatic.com, invalid data for pmzoneid', () => {
+ adapter.callBids(createBidderRequest({
+ params: {
+ publisherId: 9990,
+ adSlot: 'abcd@728x90',
+ age: '20',
+ wiid: 'abcdefghijk',
+ profId: '1234',
+ verId: '12',
+ pmzoneid: {},
+ dctr: 1234
+ }
+ }));
+ var callURL = utils.createContentToExecuteExtScriptInFriendlyFrame.getCall(0).args[0];
+ expect(callURL).to.contain('gads.pubmatic.com/AdServer/AdCallAggregator?');
+ expect(callURL).to.not.contain('pmZoneId=');
+ });
+ });
+
+ describe('#handlePubmaticCallback: ', () => {
+ beforeEach(() => {
+ sinon.stub(utils, 'createContentToExecuteExtScriptInFriendlyFrame', function() {
+ return '';
+ });
+ sinon.stub(bidmanager, 'addBidResponse');
+ });
+
+ afterEach(() => {
+ utils.createContentToExecuteExtScriptInFriendlyFrame.restore();
+ bidmanager.addBidResponse.restore();
+ });
+
+ it('exists and is a function', () => {
+ expect($$PREBID_GLOBAL$$.handlePubmaticCallback).to.exist.and.to.be.a('function');
+ });
+
+ it('empty response, arguments not passed', () => {
+ adapter.callBids(createBidderRequest({
+ params: {
+ publisherId: 9999,
+ adSlot: 'abcd@728x90',
+ age: '20'
+ }
+ }));
+ $$PREBID_GLOBAL$$.handlePubmaticCallback();
+ expect(bidmanager.addBidResponse.callCount).to.equal(0);
+ });
+
+ it('empty response', () => {
+ adapter.callBids(createBidderRequest({
+ params: {
+ publisherId: 9999,
+ adSlot: 'abcd@728x90',
+ age: '20'
+ }
+ }));
+ $$PREBID_GLOBAL$$.handlePubmaticCallback({}, {});
+ sinon.assert.called(bidmanager.addBidResponse);
+ expect(bidmanager.addBidResponse.firstCall.args[0]).to.equal('DIV_1');
+ var theBid = bidmanager.addBidResponse.firstCall.args[1];
+ expect(theBid.bidderCode).to.equal('pubmatic');
+ expect(theBid.getStatusCode()).to.equal(2);
+ });
+
+ it('not empty response', () => {
+ adapter.callBids(createBidderRequest({
+ params: {
+ publisherId: 9999,
+ adSlot: 'abcd@728x90:0',
+ age: '20'
+ }
+ }));
+ $$PREBID_GLOBAL$$.handlePubmaticCallback({
+ 'abcd@728x90:0': {
+ 'ecpm': 10,
+ 'creative_tag': 'hello',
+ 'tracking_url': 'http%3a%2f%2fhaso.pubmatic.com%2fads%2f9999%2fGRPBID%2f2.gif%3ftrackid%3d12345',
+ 'width': 728,
+ 'height': 90,
+ 'deal_channel': 5
+ }
+ }, {
+ 'abcd@728x90:0': 'bidstatus;1;bid;10.0000;bidid;abcd@728x90:0;wdeal;PMERW36842'
+ });
+ sinon.assert.called(bidmanager.addBidResponse);
+ expect(bidmanager.addBidResponse.firstCall.args[0]).to.equal('DIV_1');
+ var theBid = bidmanager.addBidResponse.firstCall.args[1];
+ expect(theBid.bidderCode).to.equal('pubmatic');
+ expect(theBid.adSlot).to.equal('abcd@728x90:0');
+ expect(theBid.cpm).to.equal(10);
+ expect(theBid.width).to.equal(728);
+ expect(theBid.height).to.equal(90);
+ expect(theBid.dealId).to.equal('PMERW36842');
+ expect(theBid.dealChannel).to.equal('PREF');
+ });
+
+ it('not empty response, without dealChannel', () => {
+ adapter.callBids(createBidderRequest({
+ params: {
+ publisherId: 9999,
+ adSlot: 'abcd@728x90',
+ age: '20'
+ }
+ }));
+ $$PREBID_GLOBAL$$.handlePubmaticCallback({
+ 'abcd@728x90': {
+ 'ecpm': 10,
+ 'creative_tag': 'hello',
+ 'tracking_url': 'http%3a%2f%2fhaso.pubmatic.com%2fads%2f9999%2fGRPBID%2f2.gif%3ftrackid%3d12345',
+ 'width': 728,
+ 'height': 90
+ }
+ }, {
+ 'abcd@728x90': 'bidstatus;1;bid;10.0000;bidid;abcd@728x90:0;wdeal;PMERW36842'
+ });
+ sinon.assert.called(bidmanager.addBidResponse);
+ expect(bidmanager.addBidResponse.firstCall.args[0]).to.equal('DIV_1');
+ var theBid = bidmanager.addBidResponse.firstCall.args[1];
+ expect(theBid.bidderCode).to.equal('pubmatic');
+ expect(theBid.adSlot).to.equal('abcd@728x90');
+ expect(theBid.cpm).to.equal(10);
+ expect(theBid.width).to.equal(728);
+ expect(theBid.height).to.equal(90);
+ expect(theBid.dealId).to.equal('PMERW36842');
+ expect(theBid.dealChannel).to.equal(null);
+ });
+ });
+ });
+});
diff --git a/test/spec/utils_spec.js b/test/spec/utils_spec.js
index ad2645b2351..a08abaee847 100755
--- a/test/spec/utils_spec.js
+++ b/test/spec/utils_spec.js
@@ -658,6 +658,20 @@ describe('Utils', function () {
});
});
+ describe('createContentToExecuteExtScriptInFriendlyFrame', function () {
+ it('should return empty string if url is not passed', function () {
+ var output = utils.createContentToExecuteExtScriptInFriendlyFrame();
+ assert.equal(output, '');
+ });
+
+ it('should have URL in returned value if url is passed', function () {
+ var url = 'https://abcd.com/service?a=1&b=2&c=3';
+ var output = utils.createContentToExecuteExtScriptInFriendlyFrame(url);
+ var expected = ``;
+ assert.equal(output, expected);
+ });
+ });
+
describe('getDefinedParams', () => {
it('builds an object consisting of defined params', () => {
const adUnit = {