diff --git a/modules/iasRtdProvider.js b/modules/iasRtdProvider.js index 25ca39c23d6..6f7b2d5215d 100644 --- a/modules/iasRtdProvider.js +++ b/modules/iasRtdProvider.js @@ -1,13 +1,18 @@ -import { isArray, getAdUnitSizes, getKeys, logError } from '../src/utils.js'; import { submodule } from '../src/hook.js'; +import * as utils from '../src/utils.js'; +import { ajax } from '../src/ajax.js'; import { getGlobal } from '../src/prebidGlobal.js'; -import { ajaxBuilder } from '../src/ajax.js'; /** @type {string} */ const MODULE_NAME = 'realTimeData'; const SUBMODULE_NAME = 'ias'; - -let bidResponses = {}; +const IAS_HOST = 'https://pixel.adsafeprotected.com/services/pub'; +export let iasTargeting = {}; +const BRAND_SAFETY_OBJECT_FIELD_NAME = 'brandSafety'; +const FRAUD_FIELD_NAME = 'fr'; +const SLOTS_OBJECT_FIELD_NAME = 'slots'; +const CUSTOM_FIELD_NAME = 'custom'; +const IAS_KW = 'ias-kw'; /** * Module init @@ -16,12 +21,17 @@ let bidResponses = {}; * @return {boolean} */ export function init(config, userConsent) { + const params = config.params; + if (!params || !params.pubId) { + utils.logError('missing pubId param for IAS provider'); + return false; + } return true; } function stringifySlotSizes(sizes) { let result = ''; - if (isArray(sizes)) { + if (utils.isArray(sizes)) { result = sizes.reduce((acc, size) => { acc.push(size.join('.')); return acc; @@ -31,13 +41,14 @@ function stringifySlotSizes(sizes) { return result; } -function stringifySlot(bidRequest, adUnitPath) { - const sizes = getAdUnitSizes(bidRequest); +function stringifySlot(bidRequest) { + const sizes = utils.getAdUnitSizes(bidRequest); const id = bidRequest.code; const ss = stringifySlotSizes(sizes); - const p = bidRequest.code; + const adSlot = utils.getGptSlotInfoForAdUnitCode(bidRequest.code); + const p = utils.isEmpty(adSlot) ? bidRequest.code : adSlot.gptSlot; const slot = { id, ss, p }; - const keyValues = getKeys(slot).map(function (key) { + const keyValues = utils.getKeys(slot).map(function (key) { return [key, slot[key]].join(':'); }); return '{' + keyValues.join(',') + '}'; @@ -51,35 +62,29 @@ function stringifyScreenSize() { return [(window.screen && window.screen.width) || -1, (window.screen && window.screen.height) || -1].join('.'); } -function getPageLevelKeywords(response) { +function formatTargetingData(adUnit) { let result = {}; - if (response.brandSafety) { - shallowMerge(result, response.brandSafety); + if (iasTargeting[BRAND_SAFETY_OBJECT_FIELD_NAME]) { + utils.mergeDeep(result, iasTargeting[BRAND_SAFETY_OBJECT_FIELD_NAME]); + } + if (iasTargeting[FRAUD_FIELD_NAME]) { + result[FRAUD_FIELD_NAME] = iasTargeting[FRAUD_FIELD_NAME]; + } + if (iasTargeting[CUSTOM_FIELD_NAME] && IAS_KW in iasTargeting[CUSTOM_FIELD_NAME]) { + result[IAS_KW] = iasTargeting[CUSTOM_FIELD_NAME][IAS_KW]; + } + if (iasTargeting[SLOTS_OBJECT_FIELD_NAME] && adUnit in iasTargeting[SLOTS_OBJECT_FIELD_NAME]) { + utils.mergeDeep(result, iasTargeting[SLOTS_OBJECT_FIELD_NAME][adUnit]); } - result.fr = response.fr; - result.custom = response.custom; return result; } -function shallowMerge(dest, src) { - getKeys(src).reduce((dest, srcKey) => { - dest[srcKey] = src[srcKey]; - return dest; - }, dest); -} - -function getBidRequestData(reqBidsConfigObj, callback, config) { - const adUnits = reqBidsConfigObj.adUnits || getGlobal().adUnits; - let isFinish = false; - - const IAS_HOST = 'https://pixel.adsafeprotected.com/services/pub'; - const { pubId, adUnitPath } = config.params; - const anId = pubId; +function constructQueryString(anId, adUnits) { let queries = []; queries.push(['anId', anId]); queries = queries.concat(adUnits.reduce(function (acc, request) { - acc.push(['slot', stringifySlot(request, adUnitPath)]); + acc.push(['slot', stringifySlot(request)]); return acc; }, [])); @@ -87,58 +92,69 @@ function getBidRequestData(reqBidsConfigObj, callback, config) { queries.push(['sr', stringifyScreenSize()]); queries.push(['url', encodeURIComponent(window.location.href)]); - const queryString = encodeURI(queries.map(qs => qs.join('=')).join('&')); - - const ajax = ajaxBuilder(); - - ajax(`${IAS_HOST}?${queryString}`, { - success: function (response, request) { - if (!isFinish) { - if (request.status === 200) { - const iasResponse = JSON.parse(response); - const commonBidResponse = {}; - shallowMerge(commonBidResponse, getPageLevelKeywords(iasResponse)); - commonBidResponse.slots = iasResponse.slots; - bidResponses = commonBidResponse; - adUnits.forEach(adUnit => { - adUnit.bids.forEach(bid => { - const rtd = bid.rtd || {}; - const iasRtd = {}; - iasRtd[SUBMODULE_NAME] = Object.assign({}, rtd[SUBMODULE_NAME], bidResponses); - bid.rtd = Object.assign({}, rtd, iasRtd); - }); - }); - } - isFinish = true; - } - callback(); - }, - error: function () { - logError('failed to retrieve targeting information'); - callback(); - } - }); + return encodeURI(queries.map(qs => qs.join('=')).join('&')); +} + +function parseResponse(result) { + let iasResponse = {}; + try { + iasResponse = JSON.parse(result); + } catch (err) { + utils.logError('error', err); + } + iasTargeting = iasResponse; } function getTargetingData(adUnits, config, userConsent) { const targeting = {}; - Object.keys(bidResponses).forEach(key => bidResponses[key] === undefined ? delete bidResponses[key] : {}); try { - adUnits.forEach(function(adUnit) { - targeting[adUnit] = bidResponses; - }); + if (!utils.isEmpty(iasTargeting)) { + adUnits.forEach(function (adUnit) { + targeting[adUnit] = formatTargetingData(adUnit); + }); + } } catch (err) { - logError('error', err); + utils.logError('error', err); } + utils.logInfo('IAS targeting', targeting); return targeting; } +export function getApiCallback() { + return { + success: function (response, req) { + if (req.status === 200) { + try { + parseResponse(response); + } catch (e) { + utils.logError('Unable to parse IAS response.', e); + } + } + }, + error: function () { + utils.logError('failed to retrieve IAS data'); + } + } +} + +function getBidRequestData(reqBidsConfigObj, callback, config, userConsent) { + const adUnits = reqBidsConfigObj.adUnits || getGlobal().adUnits; + const { pubId } = config.params; + const queryString = constructQueryString(pubId, adUnits); + ajax( + `${IAS_HOST}?${queryString}`, + getApiCallback(), + undefined, + { method: 'GET' } + ); +} + /** @type {RtdSubmodule} */ export const iasSubModule = { name: SUBMODULE_NAME, init: init, - getBidRequestData: getBidRequestData, - getTargetingData: getTargetingData + getTargetingData: getTargetingData, + getBidRequestData: getBidRequestData }; submodule(MODULE_NAME, iasSubModule); diff --git a/test/spec/modules/iasRtdProvider_spec.js b/test/spec/modules/iasRtdProvider_spec.js index e5e12355566..192b2c6e3c3 100644 --- a/test/spec/modules/iasRtdProvider_spec.js +++ b/test/spec/modules/iasRtdProvider_spec.js @@ -1,7 +1,9 @@ -import { iasSubModule } from 'modules/iasRtdProvider.js'; +import { iasSubModule, iasTargeting } from 'modules/iasRtdProvider.js'; import { expect } from 'chai'; import { server } from 'test/mocks/xhr.js'; +const responseHeader = { 'Content-Type': 'application/json' }; + describe('iasRtdProvider is a RTD provider that', function () { it('has the correct module name', function () { expect(iasSubModule.name).to.equal('ias'); @@ -10,8 +12,33 @@ describe('iasRtdProvider is a RTD provider that', function () { it('exists', function () { expect(iasSubModule.init).to.be.a('function'); }); - it('returns true', function () { - expect(iasSubModule.init()).to.equal(true); + it('returns false missing config params', function () { + const config = { + name: 'ias', + waitForIt: true, + }; + const value = iasSubModule.init(config); + expect(value).to.equal(false); + }); + it('returns false missing pubId param', function () { + const config = { + name: 'ias', + waitForIt: true, + params: {} + }; + const value = iasSubModule.init(config); + expect(value).to.equal(false); + }); + it('returns false missing pubId param', function () { + const config = { + name: 'ias', + waitForIt: true, + params: { + pubId: '123456' + } + }; + const value = iasSubModule.init(config); + expect(value).to.equal(true); }); }); describe('has a method `getBidRequestData` that', function () { @@ -30,10 +57,17 @@ describe('iasRtdProvider is a RTD provider that', function () { const adUnitsOriginal = adUnits; iasSubModule.getBidRequestData({ adUnits: adUnits }, callback, config); request = server.requests[0]; - server.respond(); + request.respond(200, responseHeader, JSON.stringify(data)); expect(request.url).to.be.include(`https://pixel.adsafeprotected.com/services/pub?anId=1234`); expect(adUnits).to.length(2); expect(adUnits[0]).to.be.eq(adUnitsOriginal[0]); + const targetingKeys = Object.keys(iasTargeting); + const dataKeys = Object.keys(data); + expect(targetingKeys.length).to.equal(dataKeys.length); + expect(targetingKeys['fr']).to.be.eq(dataKeys['fr']); + expect(targetingKeys['brandSafety']).to.be.eq(dataKeys['brandSafety']); + expect(targetingKeys['ias-kw']).to.be.eq(dataKeys['ias-kw']); + expect(targetingKeys['slots']).to.be.eq(dataKeys['slots']); }); }); @@ -42,10 +76,32 @@ describe('iasRtdProvider is a RTD provider that', function () { expect(iasSubModule.getTargetingData).to.be.a('function'); }); it('invoke method', function () { - const targeting = iasSubModule.getTargetingData(adUnits, config); - expect(adUnits).to.length(2); + const targeting = iasSubModule.getTargetingData(adUnitsCode, config); + expect(adUnitsCode).to.length(2); expect(targeting).to.be.not.null; expect(targeting).to.be.not.empty; + expect(targeting['one-div-id']).to.be.not.null; + const targetingKeys = Object.keys(targeting['one-div-id']); + expect(targetingKeys.length).to.equal(10); + expect(targetingKeys['adt']).to.be.not.null; + expect(targetingKeys['alc']).to.be.not.null; + expect(targetingKeys['dlm']).to.be.not.null; + expect(targetingKeys['drg']).to.be.not.null; + expect(targetingKeys['hat']).to.be.not.null; + expect(targetingKeys['off']).to.be.not.null; + expect(targetingKeys['vio']).to.be.not.null; + expect(targetingKeys['fr']).to.be.not.null; + expect(targetingKeys['ias-kw']).to.be.not.null; + expect(targetingKeys['id']).to.be.not.null; + expect(targeting['one-div-id']['adt']).to.be.eq('veryLow'); + expect(targeting['one-div-id']['alc']).to.be.eq('veryLow'); + expect(targeting['one-div-id']['dlm']).to.be.eq('veryLow'); + expect(targeting['one-div-id']['drg']).to.be.eq('veryLow'); + expect(targeting['one-div-id']['hat']).to.be.eq('veryLow'); + expect(targeting['one-div-id']['off']).to.be.eq('veryLow'); + expect(targeting['one-div-id']['vio']).to.be.eq('veryLow'); + expect(targeting['one-div-id']['fr']).to.be.eq('false'); + expect(targeting['one-div-id']['id']).to.be.eq('4813f7a2-1f22-11ec-9bfd-0a1107f94461'); }); }); }); @@ -58,6 +114,8 @@ const config = { } }; +const adUnitsCode = ['one-div-id', 'two-div-id']; + const adUnits = [ { code: 'one-div-id', @@ -87,3 +145,10 @@ const adUnits = [ } }] }]; + +const data = { + brandSafety: { adt: 'veryLow', alc: 'veryLow', dlm: 'veryLow', drg: 'veryLow', hat: 'veryLow', off: 'veryLow', vio: 'veryLow' }, + custom: { 'ias-kw': ['IAS_5995_KW', 'IAS_7066_KW', 'IAS_7232_KW', 'IAS_7364_KW', 'IAS_3894_KW', 'IAS_6535_KW', 'IAS_6153_KW', 'IAS_5238_KW', 'IAS_7393_KW', 'IAS_1499_KW', 'IAS_7376_KW', 'IAS_1035_KW', 'IAS_6566_KW', 'IAS_1058_KW', 'IAS_11338_724_KW', 'IAS_7301_KW', 'IAS_15969_725_KW', 'IAS_6358_KW', 'IAS_710_KW', 'IAS_5445_KW', 'IAS_3822_KW', 'IAS_4901_KW', 'IAS_5806_KW', 'IAS_460_KW', 'IAS_11461_702_KW', 'IAS_5681_KW', 'IAS_17609_1240_KW', 'IAS_6634_KW', 'IAS_5597_KW'] }, + fr: 'false', + slots: { 'one-div-id': { id: '4813f7a2-1f22-11ec-9bfd-0a1107f94461' } } +};