From 478dc6bff9096f813838002ff4bb1b7b85113aa9 Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 7 Feb 2022 12:18:28 +0200 Subject: [PATCH 01/76] create taboola adapter --- modules/taboolaBidAdapter.js | 153 +++++++++++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 modules/taboolaBidAdapter.js diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js new file mode 100644 index 00000000000..c9019a8e1e4 --- /dev/null +++ b/modules/taboolaBidAdapter.js @@ -0,0 +1,153 @@ +// jshint esversion: 6, es3: false, node: true +'use strict'; + +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'taboola'; +const GVLID = 42; +const CURRENCY = 'USD'; +const END_POINT_URL = 'http://taboolahb.bidder.taboolasyndication.com' + +export const spec = { + supportedMediaTypes: [BANNER], + gvlid: GVLID, + code: BIDDER_CODE, + isBidRequestValid: (bidRequest) => { + return !!(bidRequest.sizes && + bidRequest.params && + bidRequest.params.publisherId && + bidRequest.params.tagId); + }, + getUserSyncs: (syncOptions, responses, gdprConsent, uspConsent) => { + return []; + }, + buildRequests: (validBidRequests, bidderRequest) => { + const [bidRequest] = validBidRequests; + const {bcat = [], badv = [], publisherId} = bidRequest.params; + const site = getSiteProperties(bidRequest.params, bidderRequest.refererInfo.referer); + const device = {ua: navigator.userAgent}; + const timeout = bidderRequest.timeout; + const imps = getImps(validBidRequests); + + const request = { + id: bidderRequest.auctionId, + imp: imps, + site, + device, + source: {fd: 1}, + tmax: timeout, + bcat: bcat, + badv: badv + }; + const url = [END_POINT_URL, publisherId].join('?p='); + + return { + url, + method: 'POST', + data: JSON.stringify(request), + bids: validBidRequests + }; + }, + interpretResponse: (serverResponse, {bids}) => { + if (!bids) { + return []; + } + + const {bidResponses, cur: currency} = getBidResponses(serverResponse); + + if (!bidResponses) { + return []; + } + + return bids.map((bid, id) => { + const bidResponse = bidResponses[id]; + if (!bidResponse) { + return; + } + + const {price: cpm, crid: creativeId, adm: ad, w: width, h: height, adomain: advertiserDomains, meta = {} + } = bidResponse; + + if (advertiserDomains && advertiserDomains.length > 0) { + meta.advertiserDomains = advertiserDomains + } + + return { + requestId: bid.bidId, + ttl: 360, + mediaType: BANNER, + cpm, + creativeId, + currency, + ad, + width, + height, + meta, + netRevenue: false + }; + }).filter(Boolean); + }, +}; + +registerBidder(spec); + +function getSiteProperties({publisherId, bcat = []}, page) { + return { + id: publisherId, + name: publisherId, + domain: window.location.host, + page, + cat: bcat, + publisher: { + id: publisherId + }, + content: { + language: navigator.language + } + } +} + +function getImps(validBidRequests) { + return validBidRequests.map((bid, id) => { + const {tagId, bidfloor = null, bidfloorcur = CURRENCY} = bid.params; + + return { + id: id + 1, + banner: getBanners(bid), + tagid: tagId, + bidfloor, + bidfloorcur, + }; + }); +} + +function getBanners(bid) { + return getSizes(bid.sizes); +} + +function getSizes(sizes) { + return sizes.map(size => { + return { + h: size[0], + w: size[1] + } + }) +} + +function getBidResponses({body}) { + if (!body || (body && !body.bidResponse)) { + return []; + } + + const {seatbid, cur} = body.bidResponse; + + if (!seatbid.length && !seatbid[0] && seatbid[0].bids) { + return []; + } + + return { + bidResponses: seatbid[0].bid, + cur + }; +} From bb759976595e42b33852fb772026483b0ec9d244 Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 7 Feb 2022 12:35:51 +0200 Subject: [PATCH 02/76] create taboola adapter md --- modules/taboolaBidAdapter.md | 43 ++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 modules/taboolaBidAdapter.md diff --git a/modules/taboolaBidAdapter.md b/modules/taboolaBidAdapter.md new file mode 100644 index 00000000000..194f5bec7fe --- /dev/null +++ b/modules/taboolaBidAdapter.md @@ -0,0 +1,43 @@ +# Overview + +``` +Module Name: Taboola Adapter +Module Type: Bidder Adapter +Maintainer: someone@taboola.com //todo: need to fill this Maintainer email. +``` + +# Description + +Module that connects to Taboola bidder to fetch bids. +support display format. Using OpenRTB standard. + +# Configuration + +## Bidder and usersync URLs + +The Outbrain adapter does not work without setting the correct bidder and usersync URLs. +You will receive the URLs when contacting us. + +# Test Display Parameters +``` + var adUnits = [{ + code: 'your-unit-container-id', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]], + } + }, + bids: [{ + bidder: 'taboola', + params: { + tagId: 'test-1', + publisherId: 'test', + bidfloor: 0.25, // optional default is null + bidfloorcur: 'USD', // optional default is USD + bcat: ['IAB1-1'], // optional default is [] + badv: ['example.com'] // optional default is [] + } + }] + }]; + +``` From a8fe09790fac35bb8eb99d0659a367ca0c10dd69 Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 7 Feb 2022 17:38:50 +0200 Subject: [PATCH 03/76] taboolaBidAdapter.js - small fixes taboolaBidAdapter_spec.js - new UT --- modules/taboolaBidAdapter.js | 2 +- test/spec/modules/taboolaBidAdapter_spec.js | 249 ++++++++++++++++++++ 2 files changed, 250 insertions(+), 1 deletion(-) create mode 100644 test/spec/modules/taboolaBidAdapter_spec.js diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js index c9019a8e1e4..3d57d8c9b82 100644 --- a/modules/taboolaBidAdapter.js +++ b/modules/taboolaBidAdapter.js @@ -142,7 +142,7 @@ function getBidResponses({body}) { const {seatbid, cur} = body.bidResponse; - if (!seatbid.length && !seatbid[0] && seatbid[0].bids) { + if (!seatbid.length || !seatbid[0].bid) { return []; } diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js new file mode 100644 index 00000000000..13572737211 --- /dev/null +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -0,0 +1,249 @@ +import {expect} from 'chai'; +import {spec} from 'modules/taboolaBidAdapter.js'; + +describe.only('Taboola Adapter', function () { + const commonBidRequest = { + bidder: 'taboola', + params: { + publisherId: 'publisherId', + tagId: 'placement name' + }, + bidId: 'aa43860a-4644-442a-b5e0-93f268cs4d19', + auctionId: '65746dca-26f3-4186-be13-dfa63469b1b7', + } + + const displayBidRequestParams = { + sizes: [ + [300, 250] + ] + } + + describe('isBidRequestValid', function () { + it('should fail when bid is invalid - tagId isn`t defined', function () { + const bid = { + bidder: 'taboola', + params: { + publisherId: 'publisherId' + }, + ...displayBidRequestParams + } + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) + + it('should fail when bid is invalid - publisherId isn`t defined', function () { + const bid = { + bidder: 'taboola', + params: { + tagId: 'below the article' + }, + ...displayBidRequestParams + } + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) + + it('should fail when bid is invalid - sizes isn`t defined', function () { + const bid = { + bidder: 'taboola', + params: { + publisherId: 'publisherId', + tagId: 'below the article' + }, + } + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) + + it('should succeed when bid contains valid', function () { + const bid = { + bidder: 'taboola', + params: { + publisherId: 'publisherId', + tagId: 'below the article' + }, + ...displayBidRequestParams, + } + expect(spec.isBidRequestValid(bid)).to.equal(true) + }) + }) + + describe('buildRequests', function () { + const commonBidderRequest = { + refererInfo: { + referer: 'https://example.com/' + } + } + + it('should build display request', function () { + const bidRequest = { + ...commonBidRequest, + ...displayBidRequestParams, + } + const expectedData = { + 'imp': [{ + 'id': 1, + 'banner': [{'h': displayBidRequestParams.sizes[0][0], 'w': displayBidRequestParams.sizes[0][1]}], + 'tagid': 'placement name', + 'bidfloor': null, + 'bidfloorcur': 'USD' + }], + 'site': { + 'id': 'publisherId', + 'name': 'publisherId', + 'domain': window.location.host, + 'page': 'https://example.com/', + 'cat': [], + 'publisher': {'id': 'publisherId'}, + 'content': {'language': 'en-US'} + }, + 'device': {'ua': navigator.userAgent}, + 'source': {'fd': 1}, + 'bcat': [], + 'badv': [] + }; + + const res = spec.buildRequests([bidRequest], commonBidderRequest) + + expect(res.url).to.equal('http://taboolahb.bidder.taboolasyndication.com?p=publisherId') + expect(res.data).to.deep.equal(JSON.stringify(expectedData)) + }) + + it('should pass optional parameters in request', function () { + const optionalParams = { + badv: ['adadadbcd.com'], + bcat: ['IAB25', 'IAB7-39'], + bidfloor: 0.25, + bidfloorcur: 'EUR' + } + const bidRequest = { + ...commonBidRequest, + ...displayBidRequestParams, + params: {...commonBidRequest.params, ...optionalParams} + } + + const res = spec.buildRequests([bidRequest], commonBidderRequest) + const resData = JSON.parse(res.data) + expect(resData.bcat).to.deep.equal(['IAB25', 'IAB7-39']) + expect(resData.badv).to.deep.equal(['adadadbcd.com']) + expect(resData.imp[0].bidfloor).to.deep.equal(0.25) + expect(resData.imp[0].bidfloorcur).to.deep.equal('EUR') + }); + + it('should pass bidder timeout', function () { + const bidRequest = { + ...commonBidRequest, + ...displayBidRequestParams, + } + + const bidderRequest = { + ...commonBidderRequest, + timeout: 500 + } + const res = spec.buildRequests([bidRequest], bidderRequest) + const resData = JSON.parse(res.data) + expect(resData.tmax).to.equal(500) + }); + }) + + describe('interpretResponse', function () { + const serverResponse = { + body: { + 'bidResponse': { + 'id': '49ffg4d58ef9a163a69fhgfghd4fad03621b9e036f24f7_15', + 'seatbid': [ + { + 'bid': [ + { + 'id': '0b3dd94348-134b-435f-8db5-6bf5afgfc39e86c', + 'impid': '1', + 'price': 0.342068, + 'adid': '2785119545551083381', + 'adm': '\u003chtml\u003e\n\u003chead\u003e\n\u003cmeta charset\u003d"UTF-8"\u003e\n\u003cmeta http-equiv\u003d"Content-Type" content\u003d"text/html; charset\u003dutf-8"/\u003e\u003c/head\u003e\n\u003cbody style\u003d"margin: 0px; overflow:hidden;"\u003e \n\u003cscript type\u003d"text/javascript"\u003e\nwindow.tbl_trc_domain \u003d \u0027us-trc.taboola.com\u0027;\nwindow._taboola \u003d window._taboola || [];\n_taboola.push({article:\u0027auto\u0027});\n!function (e, f, u, i) {\nif (!document.getElementById(i)){\ne.async \u003d 1;\ne.src \u003d u;\ne.id \u003d i;\nf.parentNode.insertBefore(e, f);\n}\n}(document.createElement(\u0027script\u0027),\ndocument.getElementsByTagName(\u0027script\u0027)[0],\n\u0027//cdn.taboola.com/libtrc/wattpad-placement-255/loader.js\u0027,\n\u0027tb_loader_script\u0027);\nif(window.performance \u0026\u0026 typeof window.performance.mark \u003d\u003d \u0027function\u0027)\n{window.performance.mark(\u0027tbl_ic\u0027);}\n\u003c/script\u003e\n\n\u003cdiv id\u003d"taboola-below-article-thumbnails" style\u003d"height: 250px; width: 300px;"\u003e\u003c/div\u003e\n\u003cscript type\u003d"text/javascript"\u003e\nwindow._taboola \u003d window._taboola || [];\n_taboola.push({\nmode: \u0027Rbox_300x250_1x1\u0027,\ncontainer: \u0027taboola-below-article-thumbnails\u0027,\nplacement: \u0027wattpad.com_P18694_S257846_W300_H250_N1_TB\u0027,\ntarget_type: \u0027mix\u0027,\n"rtb-win":{ \nbi:\u002749ff4d58ef9a163a696d4fad03621b9e036f24f7_15\u0027,\ncu:\u0027USD\u0027,\nwp:\u0027${AUCTION_PRICE:BF}\u0027,\nwcb:\u0027~!audex-display-impression!~\u0027,\nrt:\u00271643227025284\u0027,\nrdc:\u0027us.taboolasyndication.com\u0027,\nti:\u00274212\u0027,\nex:\u0027MagniteSCoD\u0027,\nbs:\u0027xapi:257846:lvvSm6Ak7_wE\u0027,\nbp:\u002718694\u0027,\nbd:\u0027wattpad.com\u0027,\nsi:\u00279964\u0027\n} \n,\nrec: {"trc":{"si":"a69c7df43b2334f0aa337c37e2d80c21","sd":"v2_a69c7df43b2334f0aa337c37e2d80c21_3c70f7c7d64a65b15e4a4175c9a2cfa51072f04bMagniteSCoD_1643227025_1643227025_CJS1tQEQ5NdWGPLA0d76xo-9ngEgASgEMCY4iegHQIroB0iB09kDUKPPB1gAYABop-G2i_Hl-eVucAA","ui":"3c70f7c7d64a65b15e4a4175c9a2cfa51072f04bMagniteSCoD","plc":"PHON","wi":"-643136642229425433","cc":"CA","route":"US:US:V","el2r":["bulk-metrics","debug","social","metrics","perf"],"uvpw":"1","pi":"1420260","cpb":"GNO629MGIJz__________wEqGXVzLnRhYm9vbGFzeW5kaWNhdGlvbi5jb20yC3RyYy1zY29kMTI5OIDwmrUMQInoB0iK6AdQgdPZA1ijzwdjCN3__________wEQ3f__________ARgjZGMI3AoQoBAYFmRjCNIDEOAGGAhkYwiWFBCcHBgYZGMI9AUQiwoYC2RjCNkUEPkcGB1kYwj0FBCeHRgfZGorNDlmZjRkNThlZjlhMTYzYTY5NmQ0ZmFkMDM2MjFiOWUwMzZmMjRmN18xNXgCgAHpbIgBrPvTxQE","dcga":{"pubConfigOverride":{"border-color":"black","font-weight":"bold","inherit-title-color":"true","module-name":"cta-lazy-module","enable-call-to-action-creative-component":"true","disable-cta-on-custom-module":"true"}},"tslt":{"p-video-overlay":{"cancel":"סגור","goto":"עבור לדף"},"read-more":{"DEFAULT_CAPTION":"%D7%A7%D7%A8%D7%90%20%D7%A2%D7%95%D7%93"},"next-up":{"BTN_TEXT":"לקריאת התוכן הבא"},"time-ago":{"now":"עכשיו","today":"היום","yesterday":"אתמול","minutes":"לפני {0} דקות","hour":"לפני שעה","hours":"לפני {0} שעות","days":"לפני {0} ימים"},"explore-more":{"TITLE_TEXT":"המשיכו לקרוא","POPUP_TEXT":"אל תפספסו הזדמנות לקרוא עוד תוכן מעולה, רגע לפני שתעזבו"}},"evh":"-1964913910","vl":[{"ri":"185db6d274ce94b27caaabd9eed7915b","uip":"wattpad.com_P18694_S257846_W300_H250_N1_TB","ppb":"COIF","estimation_method":"EcpmEstimationMethodType_ESTIMATION","baseline_variant":"false","original_ecpm":"0.4750949889421463","v":[{"thumbnail":"https://cdn.taboola.com/libtrc/static/thumbnails/a2b272be514ca3ebe3f97a4a32a41db5.jpg","all-thumbnails":"https://cdn.taboola.com/libtrc/static/thumbnails/a2b272be514ca3ebe3f97a4a32a41db5.jpg!-#@1600x1000","origin":"default","thumb-size":"1600x1000","title":"Get Roofing Services At Prices You Can Afford In Edmonton","type":"text","published-date":"1641997069","branding-text":"Roofing Services | Search Ads","url":"https://inneth-conded.xyz/9ad2e613-8777-4fe7-9a52-386c88879289?site\u003dwattpad-placement-255\u0026site_id\u003d1420260\u0026title\u003dGet+Roofing+Services+At+Prices+You+Can+Afford+In+Edmonton\u0026platform\u003dSmartphone\u0026campaign_id\u003d15573949\u0026campaign_item_id\u003d3108610633\u0026thumbnail\u003dhttp%3A%2F%2Fcdn.taboola.com%2Flibtrc%2Fstatic%2Fthumbnails%2Fa2b272be514ca3ebe3f97a4a32a41db5.jpg\u0026cpc\u003d{cpc}\u0026click_id\u003dGiCIypnAQogsMTFL3e_mPaVM2qLvK3KRU6LWzEMUgeB6piCit1Uox6CNr5v5n-x1\u0026tblci\u003dGiCIypnAQogsMTFL3e_mPaVM2qLvK3KRU6LWzEMUgeB6piCit1Uox6CNr5v5n-x1#tblciGiCIypnAQogsMTFL3e_mPaVM2qLvK3KRU6LWzEMUgeB6piCit1Uox6CNr5v5n-x1","duration":"0","sig":"328243c4127ff16e3fdcd7270bab908f6f3fc5b4c98d","item-id":"~~V1~~2785119550041083381~~PnBkfBE9JnQxpahv0adkcuIcmMhroRAHXwLZd-7zhunTxvAnL2wqac4MyzR7uD46gj3kUkbS3FhelBtnsiJV6MhkDZRZzzIqDobN6rWmCPA3hYz5D3PLat6nhIftiT1lwdxwdlxkeV_Mfb3eos_TQavImGhxk0e7psNAZxHJ9RKL2w3lppALGgQJoy2o6lkf-pOqODtX1VkgWpEEM4WsVoWOnUTAwdyGd-8yrze8CWNp752y28hl7lleicyO1vByRdbgwlJdnqyroTPEQNNEn1JRxBOSYSWt-Xm3vkPm-G4","uploader":"","is-syndicated":"true","publisher":"search","id":"~~V1~~2785119550041083381~~PnBkfBE9JnQxpahv0adkcuIcmMhroRAHXwLZd-7zhunTxvAnL2wqac4MyzR7uD46gj3kUkbS3FhelBtnsiJV6MhkDZRZzzIqDobN6rWmCPA3hYz5D3PLat6nhIftiT1lwdxwdlxkeV_Mfb3eos_TQavImGhxk0e7psNAZxHJ9RKL2w3lppALGgQJoy2o6lkf-pOqODtX1VkgWpEEM4WsVoWOnUTAwdyGd-8yrze8CWNp752y28hl7lleicyO1vByRdbgwlJdnqyroTPEQNNEn1JRxBOSYSWt-Xm3vkPm-G4","category":"home","views":"0","itp":[{"u":"https://trc.taboola.com/1326786/log/3/unip?en\u003dclickersusa","t":"c"}],"description":""}]}],"cpcud":{"upc":"0.0","upr":"0.0"}}}\n});\n\u003c/script\u003e\n\n\u003cscript type\u003d"text/javascript"\u003e\nwindow._taboola \u003d window._taboola || [];\n_taboola.push({flush: true});\n\u003c/script\u003e\n\n\u003c/body\u003e\n\u003c/html\u003e', + 'adomain': [ + 'example.xyz' + ], + 'cid': '15744349', + 'crid': '278195503434041083381', + 'w': 300, + 'h': 250, + 'lurl': 'http://us-trc.taboola.com/sample' + } + ], + 'seat': '14204545260' + } + ], + 'bidid': 'da43860a-4644-442a-b5e0-93f268cf8d19', + 'cur': 'USD' + }, + 'debugResponse': {} + } + }; + + const request = { + bids: [ + { + ...commonBidRequest, + ...displayBidRequestParams + } + ] + } + + it('should return empty array if no valid bids', function () { + const res = spec.interpretResponse(serverResponse, []) + expect(res).to.be.an('array').that.is.empty + }); + + it('should return empty array if no server response', function () { + const res = spec.interpretResponse({}, request) + expect(res).to.be.an('array').that.is.empty + }); + + it('should return empty array if server response without seatbid', function () { + const overriddenServerResponse = {...serverResponse}; + const seatbid = {...serverResponse.body.bidResponse.seatbid[0]}; + overriddenServerResponse.body.bidResponse.seatbid[0] = {}; + + const res = spec.interpretResponse(overriddenServerResponse, request) + expect(res).to.be.an('array').that.is.empty + + overriddenServerResponse.body.bidResponse.seatbid[0] = seatbid; + }); + + it('should return empty array if server response without bids', function () { + const overriddenServerResponse = {...serverResponse}; + const bid = [...serverResponse.body.bidResponse.seatbid[0].bid]; + overriddenServerResponse.body.bidResponse.seatbid[0].bid = {}; + + const res = spec.interpretResponse(overriddenServerResponse, request) + expect(res).to.be.an('array').that.is.empty + + overriddenServerResponse.body.bidResponse.seatbid[0].bid = bid; + }); + + it('should interpret display response', function () { + const [bid] = serverResponse.body.bidResponse.seatbid[0].bid; + const expectedRes = [ + { + requestId: request.bids[0].bidId, + cpm: bid.price, + creativeId: bid.crid, + ttl: 360, + netRevenue: false, + currency: serverResponse.body.bidResponse.cur, + mediaType: 'banner', + ad: bid.adm, + width: bid.w, + height: bid.h, + meta: { + 'advertiserDomains': bid.adomain + }, + } + ] + + const res = spec.interpretResponse(serverResponse, request) + expect(res).to.deep.equal(expectedRes) + }); + }) + + describe('getUserSyncs', function () { + // todo: add UT for getUserSyncs + }) +}) From 63147501356a1731982a9a72c6236b67effdd2bb Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 7 Feb 2022 18:09:36 +0200 Subject: [PATCH 04/76] taboolaBidAdapter.js - small fixes taboolaBidAdapter_spec.js - new UT --- modules/taboolaBidAdapter.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/taboolaBidAdapter.md b/modules/taboolaBidAdapter.md index 194f5bec7fe..554eb4de442 100644 --- a/modules/taboolaBidAdapter.md +++ b/modules/taboolaBidAdapter.md @@ -3,7 +3,7 @@ ``` Module Name: Taboola Adapter Module Type: Bidder Adapter -Maintainer: someone@taboola.com //todo: need to fill this Maintainer email. +Maintainer: headerbidding@taboola.com ``` # Description From 644ba0fdcb20195d60784e56a2684be730859a06 Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 7 Feb 2022 18:15:58 +0200 Subject: [PATCH 05/76] update the md --- modules/taboolaBidAdapter.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/modules/taboolaBidAdapter.md b/modules/taboolaBidAdapter.md index 554eb4de442..42871b20ed5 100644 --- a/modules/taboolaBidAdapter.md +++ b/modules/taboolaBidAdapter.md @@ -13,11 +13,6 @@ support display format. Using OpenRTB standard. # Configuration -## Bidder and usersync URLs - -The Outbrain adapter does not work without setting the correct bidder and usersync URLs. -You will receive the URLs when contacting us. - # Test Display Parameters ``` var adUnits = [{ From b09a481b76347d90540e19263c3296f40206b3b7 Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 8 Feb 2022 09:12:02 +0200 Subject: [PATCH 06/76] update the Maintainer email --- modules/taboolaBidAdapter.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/taboolaBidAdapter.md b/modules/taboolaBidAdapter.md index 42871b20ed5..f809e66d9b8 100644 --- a/modules/taboolaBidAdapter.md +++ b/modules/taboolaBidAdapter.md @@ -3,7 +3,7 @@ ``` Module Name: Taboola Adapter Module Type: Bidder Adapter -Maintainer: headerbidding@taboola.com +Maintainer: prebid@taboola.com ``` # Description From 0f11f67689cb78f60a9aae60ae4fd5eae8055bb1 Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 8 Feb 2022 10:05:38 +0200 Subject: [PATCH 07/76] * update MD page * refactor code for better readability * small fix in UT --- modules/taboolaBidAdapter.js | 56 +++++++++++---------- modules/taboolaBidAdapter.md | 15 ++++-- test/spec/modules/taboolaBidAdapter_spec.js | 2 +- 3 files changed, 42 insertions(+), 31 deletions(-) diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js index 3d57d8c9b82..fda06eb09e3 100644 --- a/modules/taboolaBidAdapter.js +++ b/modules/taboolaBidAdapter.js @@ -60,33 +60,7 @@ export const spec = { return []; } - return bids.map((bid, id) => { - const bidResponse = bidResponses[id]; - if (!bidResponse) { - return; - } - - const {price: cpm, crid: creativeId, adm: ad, w: width, h: height, adomain: advertiserDomains, meta = {} - } = bidResponse; - - if (advertiserDomains && advertiserDomains.length > 0) { - meta.advertiserDomains = advertiserDomains - } - - return { - requestId: bid.bidId, - ttl: 360, - mediaType: BANNER, - cpm, - creativeId, - currency, - ad, - width, - height, - meta, - netRevenue: false - }; - }).filter(Boolean); + return bids.map((bid, id) => getBid(bid.bidId, currency, bidResponses[id])).filter(Boolean); }, }; @@ -151,3 +125,31 @@ function getBidResponses({body}) { cur }; } + +function getBid(requestId, currency, bidResponse) { + if (!bidResponse) { + return; + } + + const { + price: cpm, crid: creativeId, adm: ad, w: width, h: height, adomain: advertiserDomains, meta = {} + } = bidResponse; + + if (advertiserDomains && advertiserDomains.length > 0) { + meta.advertiserDomains = advertiserDomains + } + + return { + requestId, + ttl: 360, + mediaType: BANNER, + cpm, + creativeId, + currency, + ad, + width, + height, + meta, + netRevenue: false + }; +} diff --git a/modules/taboolaBidAdapter.md b/modules/taboolaBidAdapter.md index f809e66d9b8..31e77be2793 100644 --- a/modules/taboolaBidAdapter.md +++ b/modules/taboolaBidAdapter.md @@ -11,8 +11,6 @@ Maintainer: prebid@taboola.com Module that connects to Taboola bidder to fetch bids. support display format. Using OpenRTB standard. -# Configuration - # Test Display Parameters ``` var adUnits = [{ @@ -34,5 +32,16 @@ support display format. Using OpenRTB standard. } }] }]; - ``` + +# Parameters + +| Name | Scope | Description | Example | Type | +|---------------|----------|----------------------------------------------|--------------------------|--------------| +| `tagId` | required | Tag Id / Placement name | `below the article` | `String` | +| `publisherId` | required | Tag Id / Placement name | `below the article` | `String` | +| `bcat` | optional | list of blocked advertiser categories (IAB) | `['IAB1-1']` | `Array` | +| `badv` | optional | Blocked Advertiser Domains | `example.com` | `String Url` | +| `bidfloor` | optional | CPM bid floor | `0.25` | `Integer` | +| `bidfloorcur` | optional | CPM bid floor currency | `Euro` | `Integer` | + diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index 13572737211..2e035f11d0f 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -1,7 +1,7 @@ import {expect} from 'chai'; import {spec} from 'modules/taboolaBidAdapter.js'; -describe.only('Taboola Adapter', function () { +describe('Taboola Adapter', function () { const commonBidRequest = { bidder: 'taboola', params: { From 90b0258d775cfd8d848aae5369b404f457513e21 Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 8 Feb 2022 11:14:31 +0200 Subject: [PATCH 08/76] * add privacy to the request builder * add relevant Ut * small fixes in UT --- modules/taboolaBidAdapter.js | 34 +++++++++-- test/spec/modules/taboolaBidAdapter_spec.js | 67 ++++++++++++++++----- 2 files changed, 82 insertions(+), 19 deletions(-) diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js index fda06eb09e3..bf014c00479 100644 --- a/modules/taboolaBidAdapter.js +++ b/modules/taboolaBidAdapter.js @@ -3,6 +3,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER} from '../src/mediaTypes.js'; +import {config} from '../src/config.js'; const BIDDER_CODE = 'taboola'; const GVLID = 42; @@ -24,11 +25,31 @@ export const spec = { }, buildRequests: (validBidRequests, bidderRequest) => { const [bidRequest] = validBidRequests; + const {refererInfo, gdprConsent = {}, uspConsent} = bidderRequest; const {bcat = [], badv = [], publisherId} = bidRequest.params; - const site = getSiteProperties(bidRequest.params, bidderRequest.refererInfo.referer); + const site = getSiteProperties(bidRequest.params, refererInfo.referer); const device = {ua: navigator.userAgent}; - const timeout = bidderRequest.timeout; const imps = getImps(validBidRequests); + const user = { + ext: {} + }; + const regs = { + coppa: 0, + ext: {} + }; + + if (gdprConsent.gdprApplies) { + user.ext.consent = bidderRequest.gdprConsent.consentString; + regs.ext.gdpr = 1; + } + + if (uspConsent) { + regs.ext.us_privacy = uspConsent; + } + + if (config.getConfig('coppa')) { + regs.coppa = 1 + } const request = { id: bidderRequest.auctionId, @@ -36,10 +57,13 @@ export const spec = { site, device, source: {fd: 1}, - tmax: timeout, - bcat: bcat, - badv: badv + tmax: bidderRequest.timeout, + bcat, + badv, + user, + regs }; + const url = [END_POINT_URL, publisherId].join('?p='); return { diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index 2e035f11d0f..0d441dcba2b 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -1,5 +1,6 @@ import {expect} from 'chai'; import {spec} from 'modules/taboolaBidAdapter.js'; +import {config} from '../../../src/config' describe('Taboola Adapter', function () { const commonBidRequest = { @@ -66,6 +67,11 @@ describe('Taboola Adapter', function () { }) describe('buildRequests', function () { + const defaultBidRequest = { + ...commonBidRequest, + ...displayBidRequestParams, + } + const commonBidderRequest = { refererInfo: { referer: 'https://example.com/' @@ -73,10 +79,6 @@ describe('Taboola Adapter', function () { } it('should build display request', function () { - const bidRequest = { - ...commonBidRequest, - ...displayBidRequestParams, - } const expectedData = { 'imp': [{ 'id': 1, @@ -97,10 +99,12 @@ describe('Taboola Adapter', function () { 'device': {'ua': navigator.userAgent}, 'source': {'fd': 1}, 'bcat': [], - 'badv': [] + 'badv': [], + 'user': {'ext': {}}, + 'regs': {'coppa': 0, 'ext': {}} }; - const res = spec.buildRequests([bidRequest], commonBidderRequest) + const res = spec.buildRequests([defaultBidRequest], commonBidderRequest) expect(res.url).to.equal('http://taboolahb.bidder.taboolasyndication.com?p=publisherId') expect(res.data).to.deep.equal(JSON.stringify(expectedData)) @@ -114,8 +118,7 @@ describe('Taboola Adapter', function () { bidfloorcur: 'EUR' } const bidRequest = { - ...commonBidRequest, - ...displayBidRequestParams, + ...defaultBidRequest, params: {...commonBidRequest.params, ...optionalParams} } @@ -128,19 +131,55 @@ describe('Taboola Adapter', function () { }); it('should pass bidder timeout', function () { - const bidRequest = { - ...commonBidRequest, - ...displayBidRequestParams, - } - const bidderRequest = { ...commonBidderRequest, timeout: 500 } - const res = spec.buildRequests([bidRequest], bidderRequest) + const res = spec.buildRequests([defaultBidRequest], bidderRequest) const resData = JSON.parse(res.data) expect(resData.tmax).to.equal(500) }); + + describe('handle privacy segments when building request', function () { + it('should pass GDPR consent', function () { + const bidderRequest = { + refererInfo: { + referer: 'https://example.com/' + }, + gdprConsent: { + gdprApplies: true, + consentString: 'consentString', + } + }; + + const res = spec.buildRequests([defaultBidRequest], bidderRequest) + const resData = JSON.parse(res.data) + expect(resData.user.ext.consent).to.equal('consentString') + expect(resData.regs.ext.gdpr).to.equal(1) + }); + + it('should pass us privacy consent', function () { + const bidderRequest = { + refererInfo: { + referer: 'https://example.com/' + }, + uspConsent: 'consentString' + } + const res = spec.buildRequests([defaultBidRequest], bidderRequest) + const resData = JSON.parse(res.data) + expect(resData.regs.ext.us_privacy).to.equal('consentString') + }); + + it('should pass coppa consent', function () { + config.setConfig({coppa: true}) + + const res = spec.buildRequests([defaultBidRequest], commonBidderRequest) + const resData = JSON.parse(res.data); + expect(resData.regs.coppa).to.equal(1) + + config.resetConfig() + }); + }) }) describe('interpretResponse', function () { From de8139638f6b38110fe1f06c104394a5b3d7aa93 Mon Sep 17 00:00:00 2001 From: Michael Date: Wed, 9 Feb 2022 14:44:07 +0200 Subject: [PATCH 09/76] * code refactoring + add more accurate way to get page url and referer * add relevant Ut * small fixes in md --- modules/taboolaBidAdapter.js | 41 +++++++-- modules/taboolaBidAdapter.md | 16 ++-- test/spec/modules/taboolaBidAdapter_spec.js | 93 +++++++++++++++++++-- 3 files changed, 131 insertions(+), 19 deletions(-) diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js index bf014c00479..44863c81aef 100644 --- a/modules/taboolaBidAdapter.js +++ b/modules/taboolaBidAdapter.js @@ -4,6 +4,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; +import {getWindowSelf, getWindowTop} from '../src/utils.js' const BIDDER_CODE = 'taboola'; const GVLID = 42; @@ -27,10 +28,11 @@ export const spec = { const [bidRequest] = validBidRequests; const {refererInfo, gdprConsent = {}, uspConsent} = bidderRequest; const {bcat = [], badv = [], publisherId} = bidRequest.params; - const site = getSiteProperties(bidRequest.params, refererInfo.referer); + const site = getSiteProperties(bidRequest.params, refererInfo); const device = {ua: navigator.userAgent}; const imps = getImps(validBidRequests); const user = { + buyerid: window.TRC ? window.TRC.user_id : 0, ext: {} }; const regs = { @@ -64,7 +66,7 @@ export const spec = { regs }; - const url = [END_POINT_URL, publisherId].join('?p='); + const url = [END_POINT_URL, publisherId].join('?pid='); return { url, @@ -87,16 +89,45 @@ export const spec = { return bids.map((bid, id) => getBid(bid.bidId, currency, bidResponses[id])).filter(Boolean); }, }; +export const internal = { + getPageUrl: (refererInfo = {}) => { + if (refererInfo.canonicalUrl) { + return refererInfo.canonicalUrl; + } + + if (config.getConfig('pageUrl')) { + return config.getConfig('pageUrl'); + } + + try { + return getWindowTop().location.href; + } catch (e) { + return getWindowSelf().location.href; + } + }, + getReferrer: (refererInfo = {}) => { + if (refererInfo.referer) { + return refererInfo.referer; + } + + try { + return getWindowTop().document.referrer; + } catch (e) { + return getWindowSelf().document.referrer; + } + } +} registerBidder(spec); -function getSiteProperties({publisherId, bcat = []}, page) { +function getSiteProperties({publisherId, bcat = []}, refererInfo) { + const {getPageUrl, getReferrer} = internal; return { id: publisherId, name: publisherId, domain: window.location.host, - page, - cat: bcat, + page: getPageUrl(refererInfo), + ref: getReferrer(refererInfo), publisher: { id: publisherId }, diff --git a/modules/taboolaBidAdapter.md b/modules/taboolaBidAdapter.md index 31e77be2793..5a84ab378be 100644 --- a/modules/taboolaBidAdapter.md +++ b/modules/taboolaBidAdapter.md @@ -36,12 +36,12 @@ support display format. Using OpenRTB standard. # Parameters -| Name | Scope | Description | Example | Type | -|---------------|----------|----------------------------------------------|--------------------------|--------------| -| `tagId` | required | Tag Id / Placement name | `below the article` | `String` | -| `publisherId` | required | Tag Id / Placement name | `below the article` | `String` | -| `bcat` | optional | list of blocked advertiser categories (IAB) | `['IAB1-1']` | `Array` | -| `badv` | optional | Blocked Advertiser Domains | `example.com` | `String Url` | -| `bidfloor` | optional | CPM bid floor | `0.25` | `Integer` | -| `bidfloorcur` | optional | CPM bid floor currency | `Euro` | `Integer` | +| Name | Scope | Description | Example | Type | +|----------------|----------|-----------------------------------------------------|--------------------------|--------------| +| `tagId` | required | Tag Id / Placement name | `below the article` | `String` | +| `publisherId` | required | Publisher id | `Publisher name` | `String` | +| `bcat` | optional | list of blocked advertiser categories (IAB) | `['IAB1-1']` | `Array` | +| `badv` | optional | Blocked Advertiser Domains | `example.com` | `String Url` | +| `bidfloor` | optional | CPM bid floor | `0.25` | `Integer` | +| `bidfloorcur` | optional | CPM bid floor currency | `Euro` | `Integer` | diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index 0d441dcba2b..cb10875ff04 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -1,6 +1,7 @@ import {expect} from 'chai'; -import {spec} from 'modules/taboolaBidAdapter.js'; +import {spec, internal} from 'modules/taboolaBidAdapter.js'; import {config} from '../../../src/config' +import * as utils from '../../../src/utils' describe('Taboola Adapter', function () { const commonBidRequest = { @@ -74,7 +75,8 @@ describe('Taboola Adapter', function () { const commonBidderRequest = { refererInfo: { - referer: 'https://example.com/' + referer: 'https://example.com/ref', + canonicalUrl: 'https://example.com/' } } @@ -91,8 +93,8 @@ describe('Taboola Adapter', function () { 'id': 'publisherId', 'name': 'publisherId', 'domain': window.location.host, - 'page': 'https://example.com/', - 'cat': [], + 'page': commonBidderRequest.refererInfo.canonicalUrl, + 'ref': commonBidderRequest.refererInfo.referer, 'publisher': {'id': 'publisherId'}, 'content': {'language': 'en-US'} }, @@ -100,13 +102,16 @@ describe('Taboola Adapter', function () { 'source': {'fd': 1}, 'bcat': [], 'badv': [], - 'user': {'ext': {}}, + 'user': { + 'buyerid': 0, + 'ext': {}, + }, 'regs': {'coppa': 0, 'ext': {}} }; const res = spec.buildRequests([defaultBidRequest], commonBidderRequest) - expect(res.url).to.equal('http://taboolahb.bidder.taboolasyndication.com?p=publisherId') + expect(res.url).to.equal('http://taboolahb.bidder.taboolasyndication.com?pid=publisherId') expect(res.data).to.deep.equal(JSON.stringify(expectedData)) }) @@ -140,6 +145,20 @@ describe('Taboola Adapter', function () { expect(resData.tmax).to.equal(500) }); + it('should use Taboola user id if exist', function () { + window.TRC = { + user_id: 51525152 + } + const bidderRequest = { + ...commonBidderRequest, + timeout: 500 + } + const res = spec.buildRequests([defaultBidRequest], bidderRequest) + const resData = JSON.parse(res.data) + expect(resData.user.buyerid).to.equal(51525152) + delete window.TRC; + }); + describe('handle privacy segments when building request', function () { it('should pass GDPR consent', function () { const bidderRequest = { @@ -285,4 +304,66 @@ describe('Taboola Adapter', function () { describe('getUserSyncs', function () { // todo: add UT for getUserSyncs }) + + describe('internal functions', function () { + describe('getPageUrl', function() { + let origPageUrl; + const bidderRequest = { + refererInfo: { + canonicalUrl: 'http://canonical.url' + } + }; + + beforeEach(function() { + // remember original pageUrl in config + origPageUrl = config.getConfig('pageUrl'); + + // unset pageUrl in config + config.setConfig({ pageUrl: null }); + }); + + afterEach(function() { + // set original pageUrl to config + config.setConfig({ pageUrl: origPageUrl }); + }); + + it('should handle empty or missing data', function() { + expect(internal.getPageUrl(undefined)).to.equal(utils.getWindowTop().location.href); + expect(internal.getPageUrl('')).to.equal(utils.getWindowTop().location.href); + }); + + it('should use "pageUrl" from config', function() { + config.setConfig({ pageUrl: 'http://page.url' }); + + expect(internal.getPageUrl(undefined)).to.equal(config.getConfig('pageUrl')); + }); + + it('should use bidderRequest.refererInfo.canonicalUrl', function() { + expect(internal.getPageUrl(bidderRequest.refererInfo)).to.equal(bidderRequest.refererInfo.canonicalUrl); + }); + + it('should prefer bidderRequest.refererInfo.canonicalUrl over "pageUrl" from config', () => { + config.setConfig({ pageUrl: 'https://page.url' }); + + expect(internal.getPageUrl(bidderRequest.refererInfo)).to.equal(bidderRequest.refererInfo.canonicalUrl); + }); + }); + + describe('getReferrer', function() { + it('should handle empty or missing data', function() { + expect(internal.getReferrer(undefined)).to.equal(utils.getWindowTop().document.referrer); + expect(internal.getReferrer('')).to.equal(utils.getWindowTop().document.referrer); + }); + + it('should use bidderRequest.refererInfo.referer', () => { + const bidderRequest = { + refererInfo: { + referer: 'foobar' + } + }; + + expect(internal.getReferrer(bidderRequest.refererInfo)).to.equal(bidderRequest.refererInfo.referer); + }); + }); + }) }) From a1dfd88397b5af60b651f69e3fa3b7d22c99515f Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 10 Feb 2022 16:34:49 +0200 Subject: [PATCH 10/76] * code refactoring + gte user id * add relevant Ut * small fixes --- modules/taboolaBidAdapter.js | 117 +++++++++++----- test/spec/modules/taboolaBidAdapter_spec.js | 141 +++++++++++++++----- 2 files changed, 188 insertions(+), 70 deletions(-) diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js index 44863c81aef..34b751c5dbc 100644 --- a/modules/taboolaBidAdapter.js +++ b/modules/taboolaBidAdapter.js @@ -1,15 +1,91 @@ -// jshint esversion: 6, es3: false, node: true 'use strict'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; import {getWindowSelf, getWindowTop} from '../src/utils.js' +import {getStorageManager} from '../src/storageManager.js'; const BIDDER_CODE = 'taboola'; const GVLID = 42; const CURRENCY = 'USD'; -const END_POINT_URL = 'http://taboolahb.bidder.taboolasyndication.com' +export const END_POINT_URL = 'https://taboolahb.bidder.taboolasyndication.com'; +const USER_ID = 'user-id'; +const STORAGE_KEY = `taboola global:${USER_ID}`; +const COOKIE_KEY = 'trc_cookie_storage'; + +/** + * try to extract User Id by that order: + * local storage + * first party cookie + * rendered trc + * new user set it to 0 + */ +export const userData = { + storageManager: getStorageManager(GVLID, BIDDER_CODE), + getUserId: () => { + const {getFromLocalStorage, getFromCookie, getFromTRC} = userData; + + try { + return getFromLocalStorage() || getFromCookie() || getFromTRC(); + } catch (ex) { + return 0; + } + }, + getFromCookie() { + const {cookiesAreEnabled, getCookie} = userData.storageManager; + if (cookiesAreEnabled()) { + const cookieData = getCookie(COOKIE_KEY); + const userId = userData.getCookieDataByKey(cookieData, USER_ID); + if (userId) { + return userId; + } + } + }, + getCookieDataByKey(cookieData, key) { + const [, value = ''] = cookieData.split(`${key}=`) + return value; + }, + getFromLocalStorage() { + const {hasLocalStorage, localStorageIsEnabled, getDataFromLocalStorage} = userData.storageManager; + + if (hasLocalStorage() && localStorageIsEnabled()) { + return getDataFromLocalStorage(STORAGE_KEY); + } + }, + getFromTRC() { + return window.TRC ? window.TRC.user_id : 0; + } +} + +export const internal = { + getPageUrl: (refererInfo = {}) => { + if (refererInfo.canonicalUrl) { + return refererInfo.canonicalUrl; + } + + if (config.getConfig('pageUrl')) { + return config.getConfig('pageUrl'); + } + + try { + return getWindowTop().location.href; + } catch (e) { + return getWindowSelf().location.href; + } + }, + getReferrer: (refererInfo = {}) => { + if (refererInfo.referer) { + return refererInfo.referer; + } + + try { + return getWindowTop().document.referrer; + } catch (e) { + return getWindowSelf().document.referrer; + } + } +} export const spec = { supportedMediaTypes: [BANNER], @@ -21,9 +97,6 @@ export const spec = { bidRequest.params.publisherId && bidRequest.params.tagId); }, - getUserSyncs: (syncOptions, responses, gdprConsent, uspConsent) => { - return []; - }, buildRequests: (validBidRequests, bidderRequest) => { const [bidRequest] = validBidRequests; const {refererInfo, gdprConsent = {}, uspConsent} = bidderRequest; @@ -32,7 +105,7 @@ export const spec = { const device = {ua: navigator.userAgent}; const imps = getImps(validBidRequests); const user = { - buyerid: window.TRC ? window.TRC.user_id : 0, + buyeruid: userData.getUserId(gdprConsent, uspConsent), ext: {} }; const regs = { @@ -89,36 +162,6 @@ export const spec = { return bids.map((bid, id) => getBid(bid.bidId, currency, bidResponses[id])).filter(Boolean); }, }; -export const internal = { - getPageUrl: (refererInfo = {}) => { - if (refererInfo.canonicalUrl) { - return refererInfo.canonicalUrl; - } - - if (config.getConfig('pageUrl')) { - return config.getConfig('pageUrl'); - } - - try { - return getWindowTop().location.href; - } catch (e) { - return getWindowSelf().location.href; - } - }, - getReferrer: (refererInfo = {}) => { - if (refererInfo.referer) { - return refererInfo.referer; - } - - try { - return getWindowTop().document.referrer; - } catch (e) { - return getWindowSelf().document.referrer; - } - } -} - -registerBidder(spec); function getSiteProperties({publisherId, bcat = []}, refererInfo) { const {getPageUrl, getReferrer} = internal; @@ -208,3 +251,5 @@ function getBid(requestId, currency, bidResponse) { netRevenue: false }; } + +registerBidder(spec); diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index cb10875ff04..de1b4b30854 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -1,5 +1,5 @@ import {expect} from 'chai'; -import {spec, internal} from 'modules/taboolaBidAdapter.js'; +import {spec, internal, END_POINT_URL, userData} from 'modules/taboolaBidAdapter.js'; import {config} from '../../../src/config' import * as utils from '../../../src/utils' @@ -85,17 +85,17 @@ describe('Taboola Adapter', function () { 'imp': [{ 'id': 1, 'banner': [{'h': displayBidRequestParams.sizes[0][0], 'w': displayBidRequestParams.sizes[0][1]}], - 'tagid': 'placement name', + 'tagid': commonBidRequest.params.tagId, 'bidfloor': null, 'bidfloorcur': 'USD' }], 'site': { - 'id': 'publisherId', - 'name': 'publisherId', + 'id': commonBidRequest.params.publisherId, + 'name': commonBidRequest.params.publisherId, 'domain': window.location.host, 'page': commonBidderRequest.refererInfo.canonicalUrl, 'ref': commonBidderRequest.refererInfo.referer, - 'publisher': {'id': 'publisherId'}, + 'publisher': {'id': commonBidRequest.params.publisherId}, 'content': {'language': 'en-US'} }, 'device': {'ua': navigator.userAgent}, @@ -103,7 +103,7 @@ describe('Taboola Adapter', function () { 'bcat': [], 'badv': [], 'user': { - 'buyerid': 0, + 'buyeruid': 0, 'ext': {}, }, 'regs': {'coppa': 0, 'ext': {}} @@ -111,7 +111,7 @@ describe('Taboola Adapter', function () { const res = spec.buildRequests([defaultBidRequest], commonBidderRequest) - expect(res.url).to.equal('http://taboolahb.bidder.taboolasyndication.com?pid=publisherId') + expect(res.url).to.equal(`${END_POINT_URL}?pid=${commonBidRequest.params.publisherId}`) expect(res.data).to.deep.equal(JSON.stringify(expectedData)) }) @@ -145,20 +145,6 @@ describe('Taboola Adapter', function () { expect(resData.tmax).to.equal(500) }); - it('should use Taboola user id if exist', function () { - window.TRC = { - user_id: 51525152 - } - const bidderRequest = { - ...commonBidderRequest, - timeout: 500 - } - const res = spec.buildRequests([defaultBidRequest], bidderRequest) - const resData = JSON.parse(res.data) - expect(resData.user.buyerid).to.equal(51525152) - delete window.TRC; - }); - describe('handle privacy segments when building request', function () { it('should pass GDPR consent', function () { const bidderRequest = { @@ -199,6 +185,93 @@ describe('Taboola Adapter', function () { config.resetConfig() }); }) + + describe('handle userid ', function () { + it('should get user id from local storage', function () { + const getDataFromLocalStorage = sinon.stub(userData.storageManager, 'getDataFromLocalStorage'); + getDataFromLocalStorage.returns(51525152); + + const bidderRequest = { + ...commonBidderRequest, + timeout: 500 + } + const res = spec.buildRequests([defaultBidRequest], bidderRequest); + const resData = JSON.parse(res.data); + expect(resData.user.buyeruid).to.equal(51525152); + getDataFromLocalStorage.restore(); + }); + + it('should get user id from cookie if local storage isn`t defined', function () { + const hasLocalStorage = sinon.stub(userData.storageManager, 'hasLocalStorage'); + const getDataFromLocalStorage = sinon.stub(userData.storageManager, 'getDataFromLocalStorage'); + const getCookie = sinon.stub(userData.storageManager, 'getCookie'); + + getDataFromLocalStorage.returns(51525152); + hasLocalStorage.returns(false); + getCookie.returns('taboola%20global%3Auser-id=12121212'); + + const bidderRequest = { + ...commonBidderRequest + }; + const res = spec.buildRequests([defaultBidRequest], bidderRequest); + const resData = JSON.parse(res.data); + expect(resData.user.buyeruid).to.equal('12121212'); + + getDataFromLocalStorage.restore(); + hasLocalStorage.restore(); + getCookie.restore(); + }); + + it('should get user id from TRC if local storage and cookie isn`t defined', function () { + const hasLocalStorage = sinon.stub(userData.storageManager, 'hasLocalStorage'); + const cookiesAreEnabled = sinon.stub(userData.storageManager, 'cookiesAreEnabled'); + + hasLocalStorage.returns(false); + cookiesAreEnabled.returns(false); + + window.TRC = { + user_id: 31313131 + }; + + const bidderRequest = { + ...commonBidderRequest + } + const res = spec.buildRequests([defaultBidRequest], bidderRequest); + const resData = JSON.parse(res.data); + expect(resData.user.buyeruid).to.equal(31313131); + + hasLocalStorage.restore(); + cookiesAreEnabled.restore(); + delete window.TRC; + }); + + it('should get user id to be 0 if cookie, local storage, TRC isn`t defined', function () { + const hasLocalStorage = sinon.stub(userData.storageManager, 'hasLocalStorage'); + const cookiesAreEnabled = sinon.stub(userData.storageManager, 'cookiesAreEnabled'); + + hasLocalStorage.returns(false); + cookiesAreEnabled.returns(false); + + const bidderRequest = { + ...commonBidderRequest + } + const res = spec.buildRequests([defaultBidRequest], bidderRequest); + const resData = JSON.parse(res.data); + expect(resData.user.buyeruid).to.equal(0); + + hasLocalStorage.restore(); + cookiesAreEnabled.restore(); + }); + + it('should set buyeruid to be 0 if it`s a new user', function () { + const bidderRequest = { + ...commonBidderRequest + } + const res = spec.buildRequests([defaultBidRequest], bidderRequest); + const resData = JSON.parse(res.data); + expect(resData.user.buyeruid).to.equal(0); + }); + }); }) describe('interpretResponse', function () { @@ -301,12 +374,12 @@ describe('Taboola Adapter', function () { }); }) - describe('getUserSyncs', function () { + describe('userData', function () { // todo: add UT for getUserSyncs }) describe('internal functions', function () { - describe('getPageUrl', function() { + describe('getPageUrl', function () { let origPageUrl; const bidderRequest = { refererInfo: { @@ -314,43 +387,43 @@ describe('Taboola Adapter', function () { } }; - beforeEach(function() { + beforeEach(function () { // remember original pageUrl in config origPageUrl = config.getConfig('pageUrl'); // unset pageUrl in config - config.setConfig({ pageUrl: null }); + config.setConfig({pageUrl: null}); }); - afterEach(function() { + afterEach(function () { // set original pageUrl to config - config.setConfig({ pageUrl: origPageUrl }); + config.setConfig({pageUrl: origPageUrl}); }); - it('should handle empty or missing data', function() { + it('should handle empty or missing data', function () { expect(internal.getPageUrl(undefined)).to.equal(utils.getWindowTop().location.href); expect(internal.getPageUrl('')).to.equal(utils.getWindowTop().location.href); }); - it('should use "pageUrl" from config', function() { - config.setConfig({ pageUrl: 'http://page.url' }); + it('should use "pageUrl" from config', function () { + config.setConfig({pageUrl: 'http://page.url'}); expect(internal.getPageUrl(undefined)).to.equal(config.getConfig('pageUrl')); }); - it('should use bidderRequest.refererInfo.canonicalUrl', function() { + it('should use bidderRequest.refererInfo.canonicalUrl', function () { expect(internal.getPageUrl(bidderRequest.refererInfo)).to.equal(bidderRequest.refererInfo.canonicalUrl); }); it('should prefer bidderRequest.refererInfo.canonicalUrl over "pageUrl" from config', () => { - config.setConfig({ pageUrl: 'https://page.url' }); + config.setConfig({pageUrl: 'https://page.url'}); expect(internal.getPageUrl(bidderRequest.refererInfo)).to.equal(bidderRequest.refererInfo.canonicalUrl); }); }); - describe('getReferrer', function() { - it('should handle empty or missing data', function() { + describe('getReferrer', function () { + it('should handle empty or missing data', function () { expect(internal.getReferrer(undefined)).to.equal(utils.getWindowTop().document.referrer); expect(internal.getReferrer('')).to.equal(utils.getWindowTop().document.referrer); }); From 600a9d0c5c379ab0b140c7f3d4e6f92702065547 Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 13 Feb 2022 16:16:27 +0200 Subject: [PATCH 11/76] * code refactoring + gte user id * add relevant Ut * small fixes --- modules/taboolaBidAdapter.js | 15 +++++---------- test/spec/modules/taboolaBidAdapter_spec.js | 5 ++++- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js index 34b751c5dbc..d1c5628184d 100644 --- a/modules/taboolaBidAdapter.js +++ b/modules/taboolaBidAdapter.js @@ -195,16 +195,11 @@ function getImps(validBidRequests) { } function getBanners(bid) { - return getSizes(bid.sizes); -} - -function getSizes(sizes) { - return sizes.map(size => { - return { - h: size[0], - w: size[1] - } - }) + const [size] = bid.sizes; + return { + h: size[0], + w: size[1] + } } function getBidResponses({body}) { diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index de1b4b30854..57d49cb9f1c 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -84,7 +84,10 @@ describe('Taboola Adapter', function () { const expectedData = { 'imp': [{ 'id': 1, - 'banner': [{'h': displayBidRequestParams.sizes[0][0], 'w': displayBidRequestParams.sizes[0][1]}], + 'banner': { + 'h': displayBidRequestParams.sizes[0][0], + 'w': displayBidRequestParams.sizes[0][1] + }, 'tagid': commonBidRequest.params.tagId, 'bidfloor': null, 'bidfloorcur': 'USD' From 7fb13799be3b16e8c4906456c8cc70ed1e6a6c06 Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 15 Feb 2022 16:09:56 +0200 Subject: [PATCH 12/76] * update end point url * update UT * Update banner End point structure --- modules/taboolaBidAdapter.js | 23 +++++--- modules/taboolaBidAdapter.md | 60 +++++++++++---------- test/spec/modules/taboolaBidAdapter_spec.js | 15 +++--- 3 files changed, 55 insertions(+), 43 deletions(-) diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js index d1c5628184d..d0d9db415f9 100644 --- a/modules/taboolaBidAdapter.js +++ b/modules/taboolaBidAdapter.js @@ -9,7 +9,7 @@ import {getStorageManager} from '../src/storageManager.js'; const BIDDER_CODE = 'taboola'; const GVLID = 42; const CURRENCY = 'USD'; -export const END_POINT_URL = 'https://taboolahb.bidder.taboolasyndication.com'; +export const END_POINT_URL = 'hb.bidder.taboola.com/TaboolaHBOpenRTBRequestHandlerServlet'; const USER_ID = 'user-id'; const STORAGE_KEY = `taboola global:${USER_ID}`; const COOKIE_KEY = 'trc_cookie_storage'; @@ -93,9 +93,9 @@ export const spec = { code: BIDDER_CODE, isBidRequestValid: (bidRequest) => { return !!(bidRequest.sizes && - bidRequest.params && - bidRequest.params.publisherId && - bidRequest.params.tagId); + bidRequest.params && + bidRequest.params.publisherId && + bidRequest.params.tagId); }, buildRequests: (validBidRequests, bidderRequest) => { const [bidRequest] = validBidRequests; @@ -139,7 +139,7 @@ export const spec = { regs }; - const url = [END_POINT_URL, publisherId].join('?pid='); + const url = [END_POINT_URL, publisherId].join(''); return { url, @@ -195,10 +195,17 @@ function getImps(validBidRequests) { } function getBanners(bid) { - const [size] = bid.sizes; + return getSizes(bid.sizes); +} + +function getSizes(sizes) { return { - h: size[0], - w: size[1] + format: sizes.map(size => { + return { + h: size[0], + w: size[1] + } + }) } } diff --git a/modules/taboolaBidAdapter.md b/modules/taboolaBidAdapter.md index 5a84ab378be..d02cdf4126a 100644 --- a/modules/taboolaBidAdapter.md +++ b/modules/taboolaBidAdapter.md @@ -9,39 +9,41 @@ Maintainer: prebid@taboola.com # Description Module that connects to Taboola bidder to fetch bids. -support display format. Using OpenRTB standard. +- Supports ‘display’ format +- Uses OpenRTB standard + +The Taboola Bidding adapter requires setup before beginning. Please contact us on prebid@taboola.com # Test Display Parameters -``` - var adUnits = [{ - code: 'your-unit-container-id', - mediaTypes: { - banner: { - sizes: [[300, 250], [300,600]], - } - }, - bids: [{ - bidder: 'taboola', - params: { - tagId: 'test-1', - publisherId: 'test', - bidfloor: 0.25, // optional default is null - bidfloorcur: 'USD', // optional default is USD - bcat: ['IAB1-1'], // optional default is [] - badv: ['example.com'] // optional default is [] - } - }] - }]; +``` javascript + var adUnits = [{ + code: 'your-unit-container-id', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]] + } + }, + bids: [{ + bidder: 'taboola', + params: { + tagId: 'Placement Name', + publisherId: 'your-publisher-id', + bidfloor: 0.25, // Optional - default is null + bcat: ['IAB1-1'], // Optional - default is [] + badv: ['example.com'] // Optional - default is [] + } + }] +}]; ``` # Parameters -| Name | Scope | Description | Example | Type | -|----------------|----------|-----------------------------------------------------|--------------------------|--------------| -| `tagId` | required | Tag Id / Placement name | `below the article` | `String` | -| `publisherId` | required | Publisher id | `Publisher name` | `String` | -| `bcat` | optional | list of blocked advertiser categories (IAB) | `['IAB1-1']` | `Array` | -| `badv` | optional | Blocked Advertiser Domains | `example.com` | `String Url` | -| `bidfloor` | optional | CPM bid floor | `0.25` | `Integer` | -| `bidfloorcur` | optional | CPM bid floor currency | `Euro` | `Integer` | +| Name | Scope | Description | Example | Type | +|----------------|----------|---------------------------------------------------------|----------------------------|--------------| +| `tagId` | required | Tag ID / Placement Name
(as provided by Taboola) | `'Below The Article'` | `String` | +| `publisherId` | required | Alphabetic Publisher ID
(as provided by Taboola) | `'acme-publishing'` | `String` | +| `bcat` | optional | List of blocked advertiser categories (IAB) | `['IAB1-1']` | `Array` | +| `badv` | optional | Blocked Advertiser Domains | `'example.com'` | `String Url` | +| `bidfloor` | optional | CPM bid floor | `0.25` | `Integer` | + diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index 57d49cb9f1c..e5a34dfb36d 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -15,9 +15,7 @@ describe('Taboola Adapter', function () { } const displayBidRequestParams = { - sizes: [ - [300, 250] - ] + sizes: [[300, 250], [300, 600]] } describe('isBidRequestValid', function () { @@ -85,8 +83,13 @@ describe('Taboola Adapter', function () { 'imp': [{ 'id': 1, 'banner': { - 'h': displayBidRequestParams.sizes[0][0], - 'w': displayBidRequestParams.sizes[0][1] + format: [{ + h: displayBidRequestParams.sizes[0][0], + w: displayBidRequestParams.sizes[0][1]}, + { + h: displayBidRequestParams.sizes[1][0], + w: displayBidRequestParams.sizes[1][1]} + ] }, 'tagid': commonBidRequest.params.tagId, 'bidfloor': null, @@ -114,7 +117,7 @@ describe('Taboola Adapter', function () { const res = spec.buildRequests([defaultBidRequest], commonBidderRequest) - expect(res.url).to.equal(`${END_POINT_URL}?pid=${commonBidRequest.params.publisherId}`) + expect(res.url).to.equal(`${END_POINT_URL}${commonBidRequest.params.publisherId}`) expect(res.data).to.deep.equal(JSON.stringify(expectedData)) }) From 0cf48adc552fb29aa33d830c610226db10577e3d Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 21 Feb 2022 10:09:29 +0200 Subject: [PATCH 13/76] small fixes + update epi url --- modules/taboolaBidAdapter.js | 14 +++++++------- modules/taboolaBidAdapter.md | 4 ++-- test/spec/modules/taboolaBidAdapter_spec.js | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js index d0d9db415f9..534edcc6e63 100644 --- a/modules/taboolaBidAdapter.js +++ b/modules/taboolaBidAdapter.js @@ -9,17 +9,17 @@ import {getStorageManager} from '../src/storageManager.js'; const BIDDER_CODE = 'taboola'; const GVLID = 42; const CURRENCY = 'USD'; -export const END_POINT_URL = 'hb.bidder.taboola.com/TaboolaHBOpenRTBRequestHandlerServlet'; +export const END_POINT_URL = 'http://hb.bidder.taboola.com/TaboolaHBOpenRTBRequestHandlerServlet'; const USER_ID = 'user-id'; const STORAGE_KEY = `taboola global:${USER_ID}`; const COOKIE_KEY = 'trc_cookie_storage'; /** - * try to extract User Id by that order: - * local storage - * first party cookie - * rendered trc - * new user set it to 0 + * extract User Id by that order: + * 1. local storage + * 2. first party cookie + * 3. rendered trc + * 4. new user set it to 0 */ export const userData = { storageManager: getStorageManager(GVLID, BIDDER_CODE), @@ -139,7 +139,7 @@ export const spec = { regs }; - const url = [END_POINT_URL, publisherId].join(''); + const url = [END_POINT_URL, publisherId].join('/'); return { url, diff --git a/modules/taboolaBidAdapter.md b/modules/taboolaBidAdapter.md index d02cdf4126a..a3cd1d9261b 100644 --- a/modules/taboolaBidAdapter.md +++ b/modules/taboolaBidAdapter.md @@ -9,8 +9,8 @@ Maintainer: prebid@taboola.com # Description Module that connects to Taboola bidder to fetch bids. -- Supports ‘display’ format -- Uses OpenRTB standard +- Supports `display` format +- Uses `OpenRTB` standard The Taboola Bidding adapter requires setup before beginning. Please contact us on prebid@taboola.com diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index e5a34dfb36d..f6cbee45668 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -117,7 +117,7 @@ describe('Taboola Adapter', function () { const res = spec.buildRequests([defaultBidRequest], commonBidderRequest) - expect(res.url).to.equal(`${END_POINT_URL}${commonBidRequest.params.publisherId}`) + expect(res.url).to.equal(`${END_POINT_URL}/${commonBidRequest.params.publisherId}`) expect(res.data).to.deep.equal(JSON.stringify(expectedData)) }) From e6e0235a1512b874553334c302a6a3c8c38ec7f9 Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 8 May 2022 14:40:06 +0300 Subject: [PATCH 14/76] remove the destruction from the bidResponse property --- modules/taboolaBidAdapter.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js index 534edcc6e63..a35f75c8079 100644 --- a/modules/taboolaBidAdapter.js +++ b/modules/taboolaBidAdapter.js @@ -210,11 +210,11 @@ function getSizes(sizes) { } function getBidResponses({body}) { - if (!body || (body && !body.bidResponse)) { + if (!body) { return []; } - const {seatbid, cur} = body.bidResponse; + const {seatbid, cur} = body; if (!seatbid.length || !seatbid[0].bid) { return []; From 1dabc419d22086eed1e86cfd6ae10f160785fe2e Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 8 May 2022 15:14:37 +0300 Subject: [PATCH 15/76] (update the unit tests) remove the destruction from the bidResponse property --- modules/taboolaBidAdapter.js | 2 +- test/spec/modules/taboolaBidAdapter_spec.js | 75 ++++++++++----------- 2 files changed, 38 insertions(+), 39 deletions(-) diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js index a35f75c8079..cb6a6048d7f 100644 --- a/modules/taboolaBidAdapter.js +++ b/modules/taboolaBidAdapter.js @@ -22,7 +22,7 @@ const COOKIE_KEY = 'trc_cookie_storage'; * 4. new user set it to 0 */ export const userData = { - storageManager: getStorageManager(GVLID, BIDDER_CODE), + storageManager: getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}), getUserId: () => { const {getFromLocalStorage, getFromCookie, getFromTRC} = userData; diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index f6cbee45668..86e32843228 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -85,10 +85,12 @@ describe('Taboola Adapter', function () { 'banner': { format: [{ h: displayBidRequestParams.sizes[0][0], - w: displayBidRequestParams.sizes[0][1]}, + w: displayBidRequestParams.sizes[0][1] + }, { h: displayBidRequestParams.sizes[1][0], - w: displayBidRequestParams.sizes[1][1]} + w: displayBidRequestParams.sizes[1][1] + } ] }, 'tagid': commonBidRequest.params.tagId, @@ -283,34 +285,31 @@ describe('Taboola Adapter', function () { describe('interpretResponse', function () { const serverResponse = { body: { - 'bidResponse': { - 'id': '49ffg4d58ef9a163a69fhgfghd4fad03621b9e036f24f7_15', - 'seatbid': [ - { - 'bid': [ - { - 'id': '0b3dd94348-134b-435f-8db5-6bf5afgfc39e86c', - 'impid': '1', - 'price': 0.342068, - 'adid': '2785119545551083381', - 'adm': '\u003chtml\u003e\n\u003chead\u003e\n\u003cmeta charset\u003d"UTF-8"\u003e\n\u003cmeta http-equiv\u003d"Content-Type" content\u003d"text/html; charset\u003dutf-8"/\u003e\u003c/head\u003e\n\u003cbody style\u003d"margin: 0px; overflow:hidden;"\u003e \n\u003cscript type\u003d"text/javascript"\u003e\nwindow.tbl_trc_domain \u003d \u0027us-trc.taboola.com\u0027;\nwindow._taboola \u003d window._taboola || [];\n_taboola.push({article:\u0027auto\u0027});\n!function (e, f, u, i) {\nif (!document.getElementById(i)){\ne.async \u003d 1;\ne.src \u003d u;\ne.id \u003d i;\nf.parentNode.insertBefore(e, f);\n}\n}(document.createElement(\u0027script\u0027),\ndocument.getElementsByTagName(\u0027script\u0027)[0],\n\u0027//cdn.taboola.com/libtrc/wattpad-placement-255/loader.js\u0027,\n\u0027tb_loader_script\u0027);\nif(window.performance \u0026\u0026 typeof window.performance.mark \u003d\u003d \u0027function\u0027)\n{window.performance.mark(\u0027tbl_ic\u0027);}\n\u003c/script\u003e\n\n\u003cdiv id\u003d"taboola-below-article-thumbnails" style\u003d"height: 250px; width: 300px;"\u003e\u003c/div\u003e\n\u003cscript type\u003d"text/javascript"\u003e\nwindow._taboola \u003d window._taboola || [];\n_taboola.push({\nmode: \u0027Rbox_300x250_1x1\u0027,\ncontainer: \u0027taboola-below-article-thumbnails\u0027,\nplacement: \u0027wattpad.com_P18694_S257846_W300_H250_N1_TB\u0027,\ntarget_type: \u0027mix\u0027,\n"rtb-win":{ \nbi:\u002749ff4d58ef9a163a696d4fad03621b9e036f24f7_15\u0027,\ncu:\u0027USD\u0027,\nwp:\u0027${AUCTION_PRICE:BF}\u0027,\nwcb:\u0027~!audex-display-impression!~\u0027,\nrt:\u00271643227025284\u0027,\nrdc:\u0027us.taboolasyndication.com\u0027,\nti:\u00274212\u0027,\nex:\u0027MagniteSCoD\u0027,\nbs:\u0027xapi:257846:lvvSm6Ak7_wE\u0027,\nbp:\u002718694\u0027,\nbd:\u0027wattpad.com\u0027,\nsi:\u00279964\u0027\n} \n,\nrec: {"trc":{"si":"a69c7df43b2334f0aa337c37e2d80c21","sd":"v2_a69c7df43b2334f0aa337c37e2d80c21_3c70f7c7d64a65b15e4a4175c9a2cfa51072f04bMagniteSCoD_1643227025_1643227025_CJS1tQEQ5NdWGPLA0d76xo-9ngEgASgEMCY4iegHQIroB0iB09kDUKPPB1gAYABop-G2i_Hl-eVucAA","ui":"3c70f7c7d64a65b15e4a4175c9a2cfa51072f04bMagniteSCoD","plc":"PHON","wi":"-643136642229425433","cc":"CA","route":"US:US:V","el2r":["bulk-metrics","debug","social","metrics","perf"],"uvpw":"1","pi":"1420260","cpb":"GNO629MGIJz__________wEqGXVzLnRhYm9vbGFzeW5kaWNhdGlvbi5jb20yC3RyYy1zY29kMTI5OIDwmrUMQInoB0iK6AdQgdPZA1ijzwdjCN3__________wEQ3f__________ARgjZGMI3AoQoBAYFmRjCNIDEOAGGAhkYwiWFBCcHBgYZGMI9AUQiwoYC2RjCNkUEPkcGB1kYwj0FBCeHRgfZGorNDlmZjRkNThlZjlhMTYzYTY5NmQ0ZmFkMDM2MjFiOWUwMzZmMjRmN18xNXgCgAHpbIgBrPvTxQE","dcga":{"pubConfigOverride":{"border-color":"black","font-weight":"bold","inherit-title-color":"true","module-name":"cta-lazy-module","enable-call-to-action-creative-component":"true","disable-cta-on-custom-module":"true"}},"tslt":{"p-video-overlay":{"cancel":"סגור","goto":"עבור לדף"},"read-more":{"DEFAULT_CAPTION":"%D7%A7%D7%A8%D7%90%20%D7%A2%D7%95%D7%93"},"next-up":{"BTN_TEXT":"לקריאת התוכן הבא"},"time-ago":{"now":"עכשיו","today":"היום","yesterday":"אתמול","minutes":"לפני {0} דקות","hour":"לפני שעה","hours":"לפני {0} שעות","days":"לפני {0} ימים"},"explore-more":{"TITLE_TEXT":"המשיכו לקרוא","POPUP_TEXT":"אל תפספסו הזדמנות לקרוא עוד תוכן מעולה, רגע לפני שתעזבו"}},"evh":"-1964913910","vl":[{"ri":"185db6d274ce94b27caaabd9eed7915b","uip":"wattpad.com_P18694_S257846_W300_H250_N1_TB","ppb":"COIF","estimation_method":"EcpmEstimationMethodType_ESTIMATION","baseline_variant":"false","original_ecpm":"0.4750949889421463","v":[{"thumbnail":"https://cdn.taboola.com/libtrc/static/thumbnails/a2b272be514ca3ebe3f97a4a32a41db5.jpg","all-thumbnails":"https://cdn.taboola.com/libtrc/static/thumbnails/a2b272be514ca3ebe3f97a4a32a41db5.jpg!-#@1600x1000","origin":"default","thumb-size":"1600x1000","title":"Get Roofing Services At Prices You Can Afford In Edmonton","type":"text","published-date":"1641997069","branding-text":"Roofing Services | Search Ads","url":"https://inneth-conded.xyz/9ad2e613-8777-4fe7-9a52-386c88879289?site\u003dwattpad-placement-255\u0026site_id\u003d1420260\u0026title\u003dGet+Roofing+Services+At+Prices+You+Can+Afford+In+Edmonton\u0026platform\u003dSmartphone\u0026campaign_id\u003d15573949\u0026campaign_item_id\u003d3108610633\u0026thumbnail\u003dhttp%3A%2F%2Fcdn.taboola.com%2Flibtrc%2Fstatic%2Fthumbnails%2Fa2b272be514ca3ebe3f97a4a32a41db5.jpg\u0026cpc\u003d{cpc}\u0026click_id\u003dGiCIypnAQogsMTFL3e_mPaVM2qLvK3KRU6LWzEMUgeB6piCit1Uox6CNr5v5n-x1\u0026tblci\u003dGiCIypnAQogsMTFL3e_mPaVM2qLvK3KRU6LWzEMUgeB6piCit1Uox6CNr5v5n-x1#tblciGiCIypnAQogsMTFL3e_mPaVM2qLvK3KRU6LWzEMUgeB6piCit1Uox6CNr5v5n-x1","duration":"0","sig":"328243c4127ff16e3fdcd7270bab908f6f3fc5b4c98d","item-id":"~~V1~~2785119550041083381~~PnBkfBE9JnQxpahv0adkcuIcmMhroRAHXwLZd-7zhunTxvAnL2wqac4MyzR7uD46gj3kUkbS3FhelBtnsiJV6MhkDZRZzzIqDobN6rWmCPA3hYz5D3PLat6nhIftiT1lwdxwdlxkeV_Mfb3eos_TQavImGhxk0e7psNAZxHJ9RKL2w3lppALGgQJoy2o6lkf-pOqODtX1VkgWpEEM4WsVoWOnUTAwdyGd-8yrze8CWNp752y28hl7lleicyO1vByRdbgwlJdnqyroTPEQNNEn1JRxBOSYSWt-Xm3vkPm-G4","uploader":"","is-syndicated":"true","publisher":"search","id":"~~V1~~2785119550041083381~~PnBkfBE9JnQxpahv0adkcuIcmMhroRAHXwLZd-7zhunTxvAnL2wqac4MyzR7uD46gj3kUkbS3FhelBtnsiJV6MhkDZRZzzIqDobN6rWmCPA3hYz5D3PLat6nhIftiT1lwdxwdlxkeV_Mfb3eos_TQavImGhxk0e7psNAZxHJ9RKL2w3lppALGgQJoy2o6lkf-pOqODtX1VkgWpEEM4WsVoWOnUTAwdyGd-8yrze8CWNp752y28hl7lleicyO1vByRdbgwlJdnqyroTPEQNNEn1JRxBOSYSWt-Xm3vkPm-G4","category":"home","views":"0","itp":[{"u":"https://trc.taboola.com/1326786/log/3/unip?en\u003dclickersusa","t":"c"}],"description":""}]}],"cpcud":{"upc":"0.0","upr":"0.0"}}}\n});\n\u003c/script\u003e\n\n\u003cscript type\u003d"text/javascript"\u003e\nwindow._taboola \u003d window._taboola || [];\n_taboola.push({flush: true});\n\u003c/script\u003e\n\n\u003c/body\u003e\n\u003c/html\u003e', - 'adomain': [ - 'example.xyz' - ], - 'cid': '15744349', - 'crid': '278195503434041083381', - 'w': 300, - 'h': 250, - 'lurl': 'http://us-trc.taboola.com/sample' - } - ], - 'seat': '14204545260' - } - ], - 'bidid': 'da43860a-4644-442a-b5e0-93f268cf8d19', - 'cur': 'USD' - }, - 'debugResponse': {} + 'id': '49ffg4d58ef9a163a69fhgfghd4fad03621b9e036f24f7_15', + 'seatbid': [ + { + 'bid': [ + { + 'id': '0b3dd94348-134b-435f-8db5-6bf5afgfc39e86c', + 'impid': '1', + 'price': 0.342068, + 'adid': '2785119545551083381', + 'adm': '\u003chtml\u003e\n\u003chead\u003e\n\u003cmeta charset\u003d"UTF-8"\u003e\n\u003cmeta http-equiv\u003d"Content-Type" content\u003d"text/html; charset\u003dutf-8"/\u003e\u003c/head\u003e\n\u003cbody style\u003d"margin: 0px; overflow:hidden;"\u003e \n\u003cscript type\u003d"text/javascript"\u003e\nwindow.tbl_trc_domain \u003d \u0027us-trc.taboola.com\u0027;\nwindow._taboola \u003d window._taboola || [];\n_taboola.push({article:\u0027auto\u0027});\n!function (e, f, u, i) {\nif (!document.getElementById(i)){\ne.async \u003d 1;\ne.src \u003d u;\ne.id \u003d i;\nf.parentNode.insertBefore(e, f);\n}\n}(document.createElement(\u0027script\u0027),\ndocument.getElementsByTagName(\u0027script\u0027)[0],\n\u0027//cdn.taboola.com/libtrc/wattpad-placement-255/loader.js\u0027,\n\u0027tb_loader_script\u0027);\nif(window.performance \u0026\u0026 typeof window.performance.mark \u003d\u003d \u0027function\u0027)\n{window.performance.mark(\u0027tbl_ic\u0027);}\n\u003c/script\u003e\n\n\u003cdiv id\u003d"taboola-below-article-thumbnails" style\u003d"height: 250px; width: 300px;"\u003e\u003c/div\u003e\n\u003cscript type\u003d"text/javascript"\u003e\nwindow._taboola \u003d window._taboola || [];\n_taboola.push({\nmode: \u0027Rbox_300x250_1x1\u0027,\ncontainer: \u0027taboola-below-article-thumbnails\u0027,\nplacement: \u0027wattpad.com_P18694_S257846_W300_H250_N1_TB\u0027,\ntarget_type: \u0027mix\u0027,\n"rtb-win":{ \nbi:\u002749ff4d58ef9a163a696d4fad03621b9e036f24f7_15\u0027,\ncu:\u0027USD\u0027,\nwp:\u0027${AUCTION_PRICE:BF}\u0027,\nwcb:\u0027~!audex-display-impression!~\u0027,\nrt:\u00271643227025284\u0027,\nrdc:\u0027us.taboolasyndication.com\u0027,\nti:\u00274212\u0027,\nex:\u0027MagniteSCoD\u0027,\nbs:\u0027xapi:257846:lvvSm6Ak7_wE\u0027,\nbp:\u002718694\u0027,\nbd:\u0027wattpad.com\u0027,\nsi:\u00279964\u0027\n} \n,\nrec: {"trc":{"si":"a69c7df43b2334f0aa337c37e2d80c21","sd":"v2_a69c7df43b2334f0aa337c37e2d80c21_3c70f7c7d64a65b15e4a4175c9a2cfa51072f04bMagniteSCoD_1643227025_1643227025_CJS1tQEQ5NdWGPLA0d76xo-9ngEgASgEMCY4iegHQIroB0iB09kDUKPPB1gAYABop-G2i_Hl-eVucAA","ui":"3c70f7c7d64a65b15e4a4175c9a2cfa51072f04bMagniteSCoD","plc":"PHON","wi":"-643136642229425433","cc":"CA","route":"US:US:V","el2r":["bulk-metrics","debug","social","metrics","perf"],"uvpw":"1","pi":"1420260","cpb":"GNO629MGIJz__________wEqGXVzLnRhYm9vbGFzeW5kaWNhdGlvbi5jb20yC3RyYy1zY29kMTI5OIDwmrUMQInoB0iK6AdQgdPZA1ijzwdjCN3__________wEQ3f__________ARgjZGMI3AoQoBAYFmRjCNIDEOAGGAhkYwiWFBCcHBgYZGMI9AUQiwoYC2RjCNkUEPkcGB1kYwj0FBCeHRgfZGorNDlmZjRkNThlZjlhMTYzYTY5NmQ0ZmFkMDM2MjFiOWUwMzZmMjRmN18xNXgCgAHpbIgBrPvTxQE","dcga":{"pubConfigOverride":{"border-color":"black","font-weight":"bold","inherit-title-color":"true","module-name":"cta-lazy-module","enable-call-to-action-creative-component":"true","disable-cta-on-custom-module":"true"}},"tslt":{"p-video-overlay":{"cancel":"סגור","goto":"עבור לדף"},"read-more":{"DEFAULT_CAPTION":"%D7%A7%D7%A8%D7%90%20%D7%A2%D7%95%D7%93"},"next-up":{"BTN_TEXT":"לקריאת התוכן הבא"},"time-ago":{"now":"עכשיו","today":"היום","yesterday":"אתמול","minutes":"לפני {0} דקות","hour":"לפני שעה","hours":"לפני {0} שעות","days":"לפני {0} ימים"},"explore-more":{"TITLE_TEXT":"המשיכו לקרוא","POPUP_TEXT":"אל תפספסו הזדמנות לקרוא עוד תוכן מעולה, רגע לפני שתעזבו"}},"evh":"-1964913910","vl":[{"ri":"185db6d274ce94b27caaabd9eed7915b","uip":"wattpad.com_P18694_S257846_W300_H250_N1_TB","ppb":"COIF","estimation_method":"EcpmEstimationMethodType_ESTIMATION","baseline_variant":"false","original_ecpm":"0.4750949889421463","v":[{"thumbnail":"https://cdn.taboola.com/libtrc/static/thumbnails/a2b272be514ca3ebe3f97a4a32a41db5.jpg","all-thumbnails":"https://cdn.taboola.com/libtrc/static/thumbnails/a2b272be514ca3ebe3f97a4a32a41db5.jpg!-#@1600x1000","origin":"default","thumb-size":"1600x1000","title":"Get Roofing Services At Prices You Can Afford In Edmonton","type":"text","published-date":"1641997069","branding-text":"Roofing Services | Search Ads","url":"https://inneth-conded.xyz/9ad2e613-8777-4fe7-9a52-386c88879289?site\u003dwattpad-placement-255\u0026site_id\u003d1420260\u0026title\u003dGet+Roofing+Services+At+Prices+You+Can+Afford+In+Edmonton\u0026platform\u003dSmartphone\u0026campaign_id\u003d15573949\u0026campaign_item_id\u003d3108610633\u0026thumbnail\u003dhttp%3A%2F%2Fcdn.taboola.com%2Flibtrc%2Fstatic%2Fthumbnails%2Fa2b272be514ca3ebe3f97a4a32a41db5.jpg\u0026cpc\u003d{cpc}\u0026click_id\u003dGiCIypnAQogsMTFL3e_mPaVM2qLvK3KRU6LWzEMUgeB6piCit1Uox6CNr5v5n-x1\u0026tblci\u003dGiCIypnAQogsMTFL3e_mPaVM2qLvK3KRU6LWzEMUgeB6piCit1Uox6CNr5v5n-x1#tblciGiCIypnAQogsMTFL3e_mPaVM2qLvK3KRU6LWzEMUgeB6piCit1Uox6CNr5v5n-x1","duration":"0","sig":"328243c4127ff16e3fdcd7270bab908f6f3fc5b4c98d","item-id":"~~V1~~2785119550041083381~~PnBkfBE9JnQxpahv0adkcuIcmMhroRAHXwLZd-7zhunTxvAnL2wqac4MyzR7uD46gj3kUkbS3FhelBtnsiJV6MhkDZRZzzIqDobN6rWmCPA3hYz5D3PLat6nhIftiT1lwdxwdlxkeV_Mfb3eos_TQavImGhxk0e7psNAZxHJ9RKL2w3lppALGgQJoy2o6lkf-pOqODtX1VkgWpEEM4WsVoWOnUTAwdyGd-8yrze8CWNp752y28hl7lleicyO1vByRdbgwlJdnqyroTPEQNNEn1JRxBOSYSWt-Xm3vkPm-G4","uploader":"","is-syndicated":"true","publisher":"search","id":"~~V1~~2785119550041083381~~PnBkfBE9JnQxpahv0adkcuIcmMhroRAHXwLZd-7zhunTxvAnL2wqac4MyzR7uD46gj3kUkbS3FhelBtnsiJV6MhkDZRZzzIqDobN6rWmCPA3hYz5D3PLat6nhIftiT1lwdxwdlxkeV_Mfb3eos_TQavImGhxk0e7psNAZxHJ9RKL2w3lppALGgQJoy2o6lkf-pOqODtX1VkgWpEEM4WsVoWOnUTAwdyGd-8yrze8CWNp752y28hl7lleicyO1vByRdbgwlJdnqyroTPEQNNEn1JRxBOSYSWt-Xm3vkPm-G4","category":"home","views":"0","itp":[{"u":"https://trc.taboola.com/1326786/log/3/unip?en\u003dclickersusa","t":"c"}],"description":""}]}],"cpcud":{"upc":"0.0","upr":"0.0"}}}\n});\n\u003c/script\u003e\n\n\u003cscript type\u003d"text/javascript"\u003e\nwindow._taboola \u003d window._taboola || [];\n_taboola.push({flush: true});\n\u003c/script\u003e\n\n\u003c/body\u003e\n\u003c/html\u003e', + 'adomain': [ + 'example.xyz' + ], + 'cid': '15744349', + 'crid': '278195503434041083381', + 'w': 300, + 'h': 250, + 'lurl': 'http://us-trc.taboola.com/sample' + } + ], + 'seat': '14204545260' + } + ], + 'bidid': 'da43860a-4644-442a-b5e0-93f268cf8d19', + 'cur': 'USD' } }; @@ -335,28 +334,28 @@ describe('Taboola Adapter', function () { it('should return empty array if server response without seatbid', function () { const overriddenServerResponse = {...serverResponse}; - const seatbid = {...serverResponse.body.bidResponse.seatbid[0]}; - overriddenServerResponse.body.bidResponse.seatbid[0] = {}; + const seatbid = {...serverResponse.body.seatbid[0]}; + overriddenServerResponse.body.seatbid[0] = {}; const res = spec.interpretResponse(overriddenServerResponse, request) expect(res).to.be.an('array').that.is.empty - overriddenServerResponse.body.bidResponse.seatbid[0] = seatbid; + overriddenServerResponse.body.seatbid[0] = seatbid; }); it('should return empty array if server response without bids', function () { const overriddenServerResponse = {...serverResponse}; - const bid = [...serverResponse.body.bidResponse.seatbid[0].bid]; - overriddenServerResponse.body.bidResponse.seatbid[0].bid = {}; + const bid = [...serverResponse.body.seatbid[0].bid]; + overriddenServerResponse.body.seatbid[0].bid = {}; const res = spec.interpretResponse(overriddenServerResponse, request) expect(res).to.be.an('array').that.is.empty - overriddenServerResponse.body.bidResponse.seatbid[0].bid = bid; + overriddenServerResponse.body.seatbid[0].bid = bid; }); it('should interpret display response', function () { - const [bid] = serverResponse.body.bidResponse.seatbid[0].bid; + const [bid] = serverResponse.body.seatbid[0].bid; const expectedRes = [ { requestId: request.bids[0].bidId, @@ -364,7 +363,7 @@ describe('Taboola Adapter', function () { creativeId: bid.crid, ttl: 360, netRevenue: false, - currency: serverResponse.body.bidResponse.cur, + currency: serverResponse.body.cur, mediaType: 'banner', ad: bid.adm, width: bid.w, From 4e4ff4af384339923fda6609e5904602aa45b570 Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 9 May 2022 15:31:21 +0300 Subject: [PATCH 16/76] fix tests --- test/spec/modules/taboolaBidAdapter_spec.js | 30 +++++++++++++-------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index 86e32843228..070d6637aa2 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -4,6 +4,16 @@ import {config} from '../../../src/config' import * as utils from '../../../src/utils' describe('Taboola Adapter', function () { + let hasLocalStorage, cookiesAreEnabled, getDataFromLocalStorage, localStorageIsEnabled, getCookie; + + before(() => { + hasLocalStorage = sinon.stub(userData.storageManager, 'hasLocalStorage'); + cookiesAreEnabled = sinon.stub(userData.storageManager, 'cookiesAreEnabled'); + getCookie = sinon.stub(userData.storageManager, 'getCookie'); + getDataFromLocalStorage = sinon.stub(userData.storageManager, 'getDataFromLocalStorage'); + localStorageIsEnabled = sinon.stub(userData.storageManager, 'localStorageIsEnabled'); + }); + const commonBidRequest = { bidder: 'taboola', params: { @@ -196,8 +206,9 @@ describe('Taboola Adapter', function () { describe('handle userid ', function () { it('should get user id from local storage', function () { - const getDataFromLocalStorage = sinon.stub(userData.storageManager, 'getDataFromLocalStorage'); getDataFromLocalStorage.returns(51525152); + hasLocalStorage.returns(true); + localStorageIsEnabled.returns(true); const bidderRequest = { ...commonBidderRequest, @@ -206,16 +217,17 @@ describe('Taboola Adapter', function () { const res = spec.buildRequests([defaultBidRequest], bidderRequest); const resData = JSON.parse(res.data); expect(resData.user.buyeruid).to.equal(51525152); + getDataFromLocalStorage.restore(); + hasLocalStorage.restore(); + localStorageIsEnabled.restore(); }); it('should get user id from cookie if local storage isn`t defined', function () { - const hasLocalStorage = sinon.stub(userData.storageManager, 'hasLocalStorage'); - const getDataFromLocalStorage = sinon.stub(userData.storageManager, 'getDataFromLocalStorage'); - const getCookie = sinon.stub(userData.storageManager, 'getCookie'); - getDataFromLocalStorage.returns(51525152); hasLocalStorage.returns(false); + localStorageIsEnabled.returns(false); + cookiesAreEnabled.returns(true); getCookie.returns('taboola%20global%3Auser-id=12121212'); const bidderRequest = { @@ -223,17 +235,16 @@ describe('Taboola Adapter', function () { }; const res = spec.buildRequests([defaultBidRequest], bidderRequest); const resData = JSON.parse(res.data); + expect(resData.user.buyeruid).to.equal('12121212'); getDataFromLocalStorage.restore(); hasLocalStorage.restore(); + cookiesAreEnabled.restore(); getCookie.restore(); }); it('should get user id from TRC if local storage and cookie isn`t defined', function () { - const hasLocalStorage = sinon.stub(userData.storageManager, 'hasLocalStorage'); - const cookiesAreEnabled = sinon.stub(userData.storageManager, 'cookiesAreEnabled'); - hasLocalStorage.returns(false); cookiesAreEnabled.returns(false); @@ -254,9 +265,6 @@ describe('Taboola Adapter', function () { }); it('should get user id to be 0 if cookie, local storage, TRC isn`t defined', function () { - const hasLocalStorage = sinon.stub(userData.storageManager, 'hasLocalStorage'); - const cookiesAreEnabled = sinon.stub(userData.storageManager, 'cookiesAreEnabled'); - hasLocalStorage.returns(false); cookiesAreEnabled.returns(false); From 9f972224e39237cd25d9fcfc11f0f08365c9825e Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 9 May 2022 21:34:44 +0300 Subject: [PATCH 17/76] fix tests - run stubs on each test --- test/spec/modules/taboolaBidAdapter_spec.js | 33 +++++++++------------ 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index 070d6637aa2..f1522e72fa9 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -6,7 +6,7 @@ import * as utils from '../../../src/utils' describe('Taboola Adapter', function () { let hasLocalStorage, cookiesAreEnabled, getDataFromLocalStorage, localStorageIsEnabled, getCookie; - before(() => { + beforeEach(() => { hasLocalStorage = sinon.stub(userData.storageManager, 'hasLocalStorage'); cookiesAreEnabled = sinon.stub(userData.storageManager, 'cookiesAreEnabled'); getCookie = sinon.stub(userData.storageManager, 'getCookie'); @@ -14,6 +14,14 @@ describe('Taboola Adapter', function () { localStorageIsEnabled = sinon.stub(userData.storageManager, 'localStorageIsEnabled'); }); + afterEach(() => { + hasLocalStorage.restore(); + cookiesAreEnabled.restore(); + getCookie.restore(); + getDataFromLocalStorage.restore(); + localStorageIsEnabled.restore(); + }) + const commonBidRequest = { bidder: 'taboola', params: { @@ -114,7 +122,7 @@ describe('Taboola Adapter', function () { 'page': commonBidderRequest.refererInfo.canonicalUrl, 'ref': commonBidderRequest.refererInfo.referer, 'publisher': {'id': commonBidRequest.params.publisherId}, - 'content': {'language': 'en-US'} + 'content': {'language': navigator.language} }, 'device': {'ua': navigator.userAgent}, 'source': {'fd': 1}, @@ -127,10 +135,10 @@ describe('Taboola Adapter', function () { 'regs': {'coppa': 0, 'ext': {}} }; - const res = spec.buildRequests([defaultBidRequest], commonBidderRequest) + const res = spec.buildRequests([defaultBidRequest], commonBidderRequest); - expect(res.url).to.equal(`${END_POINT_URL}/${commonBidRequest.params.publisherId}`) - expect(res.data).to.deep.equal(JSON.stringify(expectedData)) + expect(res.url).to.equal(`${END_POINT_URL}/${commonBidRequest.params.publisherId}`); + expect(res.data).to.deep.equal(JSON.stringify(expectedData)); }) it('should pass optional parameters in request', function () { @@ -217,10 +225,6 @@ describe('Taboola Adapter', function () { const res = spec.buildRequests([defaultBidRequest], bidderRequest); const resData = JSON.parse(res.data); expect(resData.user.buyeruid).to.equal(51525152); - - getDataFromLocalStorage.restore(); - hasLocalStorage.restore(); - localStorageIsEnabled.restore(); }); it('should get user id from cookie if local storage isn`t defined', function () { @@ -237,16 +241,12 @@ describe('Taboola Adapter', function () { const resData = JSON.parse(res.data); expect(resData.user.buyeruid).to.equal('12121212'); - - getDataFromLocalStorage.restore(); - hasLocalStorage.restore(); - cookiesAreEnabled.restore(); - getCookie.restore(); }); it('should get user id from TRC if local storage and cookie isn`t defined', function () { hasLocalStorage.returns(false); cookiesAreEnabled.returns(false); + localStorageIsEnabled.returns(false); window.TRC = { user_id: 31313131 @@ -259,8 +259,6 @@ describe('Taboola Adapter', function () { const resData = JSON.parse(res.data); expect(resData.user.buyeruid).to.equal(31313131); - hasLocalStorage.restore(); - cookiesAreEnabled.restore(); delete window.TRC; }); @@ -274,9 +272,6 @@ describe('Taboola Adapter', function () { const res = spec.buildRequests([defaultBidRequest], bidderRequest); const resData = JSON.parse(res.data); expect(resData.user.buyeruid).to.equal(0); - - hasLocalStorage.restore(); - cookiesAreEnabled.restore(); }); it('should set buyeruid to be 0 if it`s a new user', function () { From 40a0cf62a8142baf0f2609f1a1476ab089b96a12 Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 9 May 2022 21:45:38 +0300 Subject: [PATCH 18/76] rerun because of another adapter flaky test --- test/spec/modules/taboolaBidAdapter_spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index f1522e72fa9..7f1b1feb1f0 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -257,7 +257,7 @@ describe('Taboola Adapter', function () { } const res = spec.buildRequests([defaultBidRequest], bidderRequest); const resData = JSON.parse(res.data); - expect(resData.user.buyeruid).to.equal(31313131); + expect(resData.user.buyeruid).to.equal(window.TRC.user_id); delete window.TRC; }); From 07a150815a15701eba98c846aa1800b273e39531 Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 9 May 2022 21:59:01 +0300 Subject: [PATCH 19/76] rerun because of another adapter flaky test --- test/spec/modules/taboolaBidAdapter_spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index 7f1b1feb1f0..994b3c2e35f 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -249,7 +249,7 @@ describe('Taboola Adapter', function () { localStorageIsEnabled.returns(false); window.TRC = { - user_id: 31313131 + user_id: 31313132 }; const bidderRequest = { From 6f734854650087a428f1907afde091e4e3f1b0ba Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 10 May 2022 16:23:31 +0300 Subject: [PATCH 20/76] fix cors issue, switch between height, width position --- modules/taboolaBidAdapter.js | 9 ++++++--- test/spec/modules/taboolaBidAdapter_spec.js | 8 ++++---- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js index cb6a6048d7f..578a94039fe 100644 --- a/modules/taboolaBidAdapter.js +++ b/modules/taboolaBidAdapter.js @@ -145,7 +145,10 @@ export const spec = { url, method: 'POST', data: JSON.stringify(request), - bids: validBidRequests + bids: validBidRequests, + options: { + withCredentials: false + }, }; }, interpretResponse: (serverResponse, {bids}) => { @@ -202,8 +205,8 @@ function getSizes(sizes) { return { format: sizes.map(size => { return { - h: size[0], - w: size[1] + w: size[0], + h: size[1] } }) } diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index 994b3c2e35f..39dd1f9105b 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -102,12 +102,12 @@ describe('Taboola Adapter', function () { 'id': 1, 'banner': { format: [{ - h: displayBidRequestParams.sizes[0][0], - w: displayBidRequestParams.sizes[0][1] + w: displayBidRequestParams.sizes[0][0], + h: displayBidRequestParams.sizes[0][1] }, { - h: displayBidRequestParams.sizes[1][0], - w: displayBidRequestParams.sizes[1][1] + w: displayBidRequestParams.sizes[1][0], + h: displayBidRequestParams.sizes[1][1] } ] }, From c13e0c20fc9f774ff62a5f87e587766dc085d1a0 Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 22 May 2022 09:31:34 +0300 Subject: [PATCH 21/76] update badv, bcat to be based in the ortb2 to support prebid 7 new protocols + update Ut --- modules/taboolaBidAdapter.js | 11 ++++++++--- test/spec/modules/taboolaBidAdapter_spec.js | 9 +++++++-- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js index 578a94039fe..f93dd39a484 100644 --- a/modules/taboolaBidAdapter.js +++ b/modules/taboolaBidAdapter.js @@ -100,7 +100,7 @@ export const spec = { buildRequests: (validBidRequests, bidderRequest) => { const [bidRequest] = validBidRequests; const {refererInfo, gdprConsent = {}, uspConsent} = bidderRequest; - const {bcat = [], badv = [], publisherId} = bidRequest.params; + const {publisherId} = bidRequest.params; const site = getSiteProperties(bidRequest.params, refererInfo); const device = {ua: navigator.userAgent}; const imps = getImps(validBidRequests); @@ -126,6 +126,11 @@ export const spec = { regs.coppa = 1 } + const ortb2 = config.getConfig('ortb2') || { + badv: [], + bcat: [] + }; + const request = { id: bidderRequest.auctionId, imp: imps, @@ -133,8 +138,8 @@ export const spec = { device, source: {fd: 1}, tmax: bidderRequest.timeout, - bcat, - badv, + bcat: ortb2.bcat, + badv: ortb2.badv, user, regs }; diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index 39dd1f9105b..cbc6b4cd6fd 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -143,11 +143,16 @@ describe('Taboola Adapter', function () { it('should pass optional parameters in request', function () { const optionalParams = { - badv: ['adadadbcd.com'], - bcat: ['IAB25', 'IAB7-39'], + bidfloor: 0.25, bidfloorcur: 'EUR' } + + config.setConfig({ortb2: { + badv: ['adadadbcd.com'], + bcat: ['IAB25', 'IAB7-39'] + }}); + const bidRequest = { ...defaultBidRequest, params: {...commonBidRequest.params, ...optionalParams} From 1a81f33c2da619f8e73929e3fe40e1993b6d60a7 Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 22 May 2022 09:44:06 +0300 Subject: [PATCH 22/76] retry run circleci --- test/spec/modules/taboolaBidAdapter_spec.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index cbc6b4cd6fd..795a436949d 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -143,7 +143,6 @@ describe('Taboola Adapter', function () { it('should pass optional parameters in request', function () { const optionalParams = { - bidfloor: 0.25, bidfloorcur: 'EUR' } From 425281ff9d8d312b6cb8ed026fa5289d20234066 Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 22 May 2022 09:53:13 +0300 Subject: [PATCH 23/76] retry run circleci --- test/spec/modules/taboolaBidAdapter_spec.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index 795a436949d..58e872aabb4 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -145,7 +145,7 @@ describe('Taboola Adapter', function () { const optionalParams = { bidfloor: 0.25, bidfloorcur: 'EUR' - } + }; config.setConfig({ortb2: { badv: ['adadadbcd.com'], @@ -155,14 +155,14 @@ describe('Taboola Adapter', function () { const bidRequest = { ...defaultBidRequest, params: {...commonBidRequest.params, ...optionalParams} - } + }; - const res = spec.buildRequests([bidRequest], commonBidderRequest) - const resData = JSON.parse(res.data) - expect(resData.bcat).to.deep.equal(['IAB25', 'IAB7-39']) - expect(resData.badv).to.deep.equal(['adadadbcd.com']) - expect(resData.imp[0].bidfloor).to.deep.equal(0.25) - expect(resData.imp[0].bidfloorcur).to.deep.equal('EUR') + const res = spec.buildRequests([bidRequest], commonBidderRequest); + const resData = JSON.parse(res.data); + expect(resData.bcat).to.deep.equal(['IAB25', 'IAB7-39']); + expect(resData.badv).to.deep.equal(['adadadbcd.com']); + expect(resData.imp[0].bidfloor).to.deep.equal(0.25); + expect(resData.imp[0].bidfloorcur).to.deep.equal('EUR'); }); it('should pass bidder timeout', function () { From 3196a745300a7f3a2ebcb55b9610e007fe96ec65 Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 22 May 2022 10:04:47 +0300 Subject: [PATCH 24/76] pull from upstream update md (placement + pub ) --- modules/taboolaBidAdapter.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/taboolaBidAdapter.md b/modules/taboolaBidAdapter.md index a3cd1d9261b..a4213355049 100644 --- a/modules/taboolaBidAdapter.md +++ b/modules/taboolaBidAdapter.md @@ -26,8 +26,8 @@ The Taboola Bidding adapter requires setup before beginning. Please contact us o bids: [{ bidder: 'taboola', params: { - tagId: 'Placement Name', - publisherId: 'your-publisher-id', + tagId: 'tester-placement', // Placement Name + publisherId: 'tester-pub', // your-publisher-id bidfloor: 0.25, // Optional - default is null bcat: ['IAB1-1'], // Optional - default is [] badv: ['example.com'] // Optional - default is [] From 637c11f2fff6a056c4703a6208d140b2ad1e0d23 Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 22 May 2022 11:59:43 +0300 Subject: [PATCH 25/76] update badv, bcat UT --- test/spec/modules/taboolaBidAdapter_spec.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index 58e872aabb4..d3b22b7291c 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -147,11 +147,6 @@ describe('Taboola Adapter', function () { bidfloorcur: 'EUR' }; - config.setConfig({ortb2: { - badv: ['adadadbcd.com'], - bcat: ['IAB25', 'IAB7-39'] - }}); - const bidRequest = { ...defaultBidRequest, params: {...commonBidRequest.params, ...optionalParams} @@ -159,8 +154,6 @@ describe('Taboola Adapter', function () { const res = spec.buildRequests([bidRequest], commonBidderRequest); const resData = JSON.parse(res.data); - expect(resData.bcat).to.deep.equal(['IAB25', 'IAB7-39']); - expect(resData.badv).to.deep.equal(['adadadbcd.com']); expect(resData.imp[0].bidfloor).to.deep.equal(0.25); expect(resData.imp[0].bidfloorcur).to.deep.equal('EUR'); }); From 7ae29954d88b1668ae0dd62c214fc3cc5dd3c059 Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 23 May 2022 14:02:24 +0300 Subject: [PATCH 26/76] rerun build --- test/spec/modules/taboolaBidAdapter_spec.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index d3b22b7291c..998bf2ef560 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -163,9 +163,9 @@ describe('Taboola Adapter', function () { ...commonBidderRequest, timeout: 500 } - const res = spec.buildRequests([defaultBidRequest], bidderRequest) - const resData = JSON.parse(res.data) - expect(resData.tmax).to.equal(500) + const res = spec.buildRequests([defaultBidRequest], bidderRequest); + const resData = JSON.parse(res.data); + expect(resData.tmax).to.equal(500); }); describe('handle privacy segments when building request', function () { From 8b564064770304be81b02ae68e6b77c5008171ba Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 23 May 2022 14:41:11 +0300 Subject: [PATCH 27/76] rerun build --- test/spec/modules/taboolaBidAdapter_spec.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index 998bf2ef560..30553bd9190 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -193,9 +193,9 @@ describe('Taboola Adapter', function () { }, uspConsent: 'consentString' } - const res = spec.buildRequests([defaultBidRequest], bidderRequest) - const resData = JSON.parse(res.data) - expect(resData.regs.ext.us_privacy).to.equal('consentString') + const res = spec.buildRequests([defaultBidRequest], bidderRequest); + const resData = JSON.parse(res.data); + expect(resData.regs.ext.us_privacy).to.equal('consentString'); }); it('should pass coppa consent', function () { From 06ecc38f44e3bd538a9ce30de59e4812bc0cdef1 Mon Sep 17 00:00:00 2001 From: haruka-yamashita2 <39541428+haruka-yamashita2@users.noreply.github.com> Date: Tue, 24 May 2022 22:24:00 +0900 Subject: [PATCH 28/76] Rename id to aoneId from dacId (#8453) --- modules/dacIdSystem.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/dacIdSystem.md b/modules/dacIdSystem.md index b422d0a536d..0239b4557e9 100644 --- a/modules/dacIdSystem.md +++ b/modules/dacIdSystem.md @@ -1,11 +1,11 @@ -## DAC User ID Submodule +## AudienceOne User ID Submodule -DAC ID, provided by [D.A.Consortium Inc.](https://www.dac.co.jp/), is ID for ad targeting by using 1st party cookie. +AudienceOne ID, provided by [D.A.Consortium Inc.](https://www.dac.co.jp/), is ID for ad targeting by using 1st party cookie. Please contact D.A.Consortium Inc. before using this ID. -## Building Prebid with DAC ID Support +## Building Prebid with AudienceOne ID Support -First, make sure to add the DAC ID submodule to your Prebid.js package with: +First, make sure to add the AudienceOne ID submodule to your Prebid.js package with: ``` gulp build --modules=dacIdSystem From c4348892b5d3425d21373e1dcf4d67da04fed622 Mon Sep 17 00:00:00 2001 From: Vikas Srivastava <30315503+visrivastava@users.noreply.github.com> Date: Tue, 24 May 2022 19:17:57 +0530 Subject: [PATCH 29/76] Akamai RTD: fixed bugs on rtd module and added the entropy values (#8284) * Fixed bugs on rtd module and added the entropy values required by Akamai DAP * Fixed the timeout issue in build browserstack tests * Fixing review comments * Fixing review comments - using storage manager for managing localStorage * Fixing review comments - using loadExternalScript method to load the script * Fixed unit test case * Fixing review comments - Added consent handling --- .../gpt/akamaidap_segments_example.html | 8 +- modules/.submodules.json | 1 + modules/akamaiDapRtdProvider.js | 586 ++++++++++++++---- modules/akamaiDapRtdProvider.md | 8 +- src/adloader.js | 3 +- .../spec/modules/akamaiDapRtdProvider_spec.js | 510 +++++++++++---- 6 files changed, 873 insertions(+), 243 deletions(-) diff --git a/integrationExamples/gpt/akamaidap_segments_example.html b/integrationExamples/gpt/akamaidap_segments_example.html index e85ac8e1337..b4e7495002e 100644 --- a/integrationExamples/gpt/akamaidap_segments_example.html +++ b/integrationExamples/gpt/akamaidap_segments_example.html @@ -68,6 +68,7 @@ } }, realTimeData: { + auctionDelay: 2000, dataProviders: [ { name: "dap", @@ -76,9 +77,10 @@ apiHostname: "prebid.dap.akadns.net", apiVersion: "x1", domain: "prebid.org", - identityType: "dap-signature:1.0.0", - segtax: 503, - tokenTtl: 5, + identityType: "dap-signature:1.3.0", + segtax: 504, + dapFpUrl: 'https://dap-dist.akamaized.net/dapfingerprinting.js', + dapFpTimeout: 1500 } } ] diff --git a/modules/.submodules.json b/modules/.submodules.json index 9402d0561fa..8d62f30d7c4 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -52,6 +52,7 @@ ], "rtdModule": [ "airgridRtdProvider", + "akamaiDapRtdProvider", "browsiRtdProvider", "dgkeywordRtdProvider", "geoedgeRtdProvider", diff --git a/modules/akamaiDapRtdProvider.js b/modules/akamaiDapRtdProvider.js index aca984d39c8..0b042da8c6e 100644 --- a/modules/akamaiDapRtdProvider.js +++ b/modules/akamaiDapRtdProvider.js @@ -10,12 +10,22 @@ import {config} from '../src/config.js'; import {getStorageManager} from '../src/storageManager.js'; import {submodule} from '../src/hook.js'; import {isPlainObject, mergeDeep, logMessage, logInfo, logError} from '../src/utils.js'; +import { loadExternalScript } from '../src/adloader.js'; const MODULE_NAME = 'realTimeData'; const SUBMODULE_NAME = 'dap'; +const MODULE_CODE = 'akamaidap'; + +export const DAP_TOKEN = 'async_dap_token'; +export const DAP_MEMBERSHIP = 'async_dap_membership'; +export const DAP_ENCRYPTED_MEMBERSHIP = 'encrypted_dap_membership'; +export const DAP_SS_ID = 'dap_ss_id'; +export const DAP_DEFAULT_TOKEN_TTL = 3600; // in seconds +export const DAP_MAX_RETRY_TOKENIZE = 1; +export const DAP_CLIENT_ENTROPY = 'dap_client_entropy' -export const SEGMENTS_STORAGE_KEY = 'akamaiDapSegments'; export const storage = getStorageManager({gvlid: null, moduleName: SUBMODULE_NAME}); +let dapRetryTokenize = 0; /** * Lazy merge objects. @@ -53,60 +63,92 @@ export function addRealTimeData(rtd) { * Real-time data retrieval from Audigent * @param {Object} reqBidsConfigObj * @param {function} onDone - * @param {Object} rtdConfi + * @param {Object} rtdConfig * @param {Object} userConsent */ export function getRealTimeData(bidConfig, onDone, rtdConfig, userConsent) { + let entropyDict = JSON.parse(storage.getDataFromLocalStorage(DAP_CLIENT_ENTROPY)); + let loadScriptPromise = new Promise((resolve, reject) => { + if (rtdConfig && rtdConfig.params && rtdConfig.params.dapFpTimeout && Number.isInteger(rtdConfig.params.dapFpTimeout)) { + setTimeout(reject, rtdConfig.params.dapFpTimeout, Error('DapFP script could not be loaded')); + } + if (entropyDict && entropyDict.expires_at > Math.round(Date.now() / 1000.0)) { + logMessage('Using cached entropy'); + resolve(); + } else { + if (typeof window.dapCalculateEntropy === 'function') { + window.dapCalculateEntropy(resolve, reject); + } else { + if (rtdConfig && rtdConfig.params && dapUtils.isValidHttpsUrl(rtdConfig.params.dapFpUrl)) { + loadExternalScript(rtdConfig.params.dapFpUrl, MODULE_CODE, () => { dapUtils.dapGetEntropy(resolve, reject) }); + } else { + reject(Error('Please check if dapFpUrl is specified and is valid under config.params')); + } + } + } + }); + loadScriptPromise + .catch((error) => { + logError('Entropy could not be calculated due to: ', error.message); + }) + .finally(() => { + generateRealTimeData(bidConfig, onDone, rtdConfig, userConsent); + }); +} + +export function generateRealTimeData(bidConfig, onDone, rtdConfig, userConsent) { logInfo('DEBUG(getRealTimeData) - ENTER'); logMessage(' - apiHostname: ' + rtdConfig.params.apiHostname); logMessage(' - apiVersion: ' + rtdConfig.params.apiVersion); - let jsonData = storage.getDataFromLocalStorage(SEGMENTS_STORAGE_KEY); + dapRetryTokenize = 0; + var jsonData = null; + if (rtdConfig && isPlainObject(rtdConfig.params)) { + if (rtdConfig.params.segtax == 504) { + let encMembership = dapUtils.dapGetEncryptedMembershipFromLocalStorage(); + if (encMembership) { + jsonData = dapUtils.dapGetEncryptedRtdObj(encMembership, rtdConfig.params.segtax) + } + } else { + let membership = dapUtils.dapGetMembershipFromLocalStorage(); + if (membership) { + jsonData = dapUtils.dapGetRtdObj(membership, rtdConfig.params.segtax) + } + } + } if (jsonData) { - let data = JSON.parse(jsonData); - if (data.rtd) { - addRealTimeData(data.rtd); + if (jsonData.rtd) { + addRealTimeData(jsonData.rtd); onDone(); logInfo('DEBUG(getRealTimeData) - 1'); // Don't return - ensure the data is always fresh. } } + // Calling setTimeout to release the main thread so that the bid request could be sent. + setTimeout(callDapAPIs, 0, bidConfig, onDone, rtdConfig, userConsent); +} +function callDapAPIs(bidConfig, onDone, rtdConfig, userConsent) { if (rtdConfig && isPlainObject(rtdConfig.params)) { let config = { api_hostname: rtdConfig.params.apiHostname, api_version: rtdConfig.params.apiVersion, domain: rtdConfig.params.domain, - segtax: rtdConfig.params.segtax - }; - let identity = { - type: rtdConfig.params.identityType + segtax: rtdConfig.params.segtax, + identity: {type: rtdConfig.params.identityType} }; - let token = dapUtils.dapGetToken(config, identity, rtdConfig.params.tokenTtl); - if (token !== null) { - let membership = dapUtils.dapGetMembership(config, token); - let udSegment = dapUtils.dapMembershipToRtbSegment(membership, config); - logMessage('DEBUG(getRealTimeData) - token: ' + token + ', user.data.segment: ', udSegment); - let data = { - rtd: { - ortb2: { - user: { - data: [ - udSegment - ] - }, - site: { - ext: { - data: { - dapSAID: membership.said - } - } - } - } - } - }; - storage.setDataInLocalStorage(SEGMENTS_STORAGE_KEY, JSON.stringify(data)); - onDone(); + let refreshMembership = true; + let token = dapUtils.dapGetTokenFromLocalStorage(); + logMessage('token is: ', token); + if (token !== null) { // If token is not null then check the membership in storage and add the RTD object + if (config.segtax == 504) { // Follow the encrypted membership path + dapUtils.dapRefreshEncryptedMembership(config, token, onDone) // Get the encrypted membership from server + refreshMembership = false; + } else { + dapUtils.dapRefreshMembership(config, token, onDone) // Get the membership from server + refreshMembership = false; + } } + dapUtils.dapRefreshToken(config, refreshMembership, onDone) // Refresh Token and membership in all the cases } } @@ -117,6 +159,9 @@ export function getRealTimeData(bidConfig, onDone, rtdConfig, userConsent) { * @return {boolean} */ function init(provider, userConsent) { + if (dapUtils.checkConsent(userConsent) === false) { + return false; + } return true; } @@ -128,104 +173,190 @@ export const akamaiDapRtdSubmodule = { }; submodule(MODULE_NAME, akamaiDapRtdSubmodule); - export const dapUtils = { + dapGetEntropy: function(resolve, reject) { + if (typeof window.dapCalculateEntropy === 'function') { + window.dapCalculateEntropy(resolve, reject); + } else { + reject(Error('window.dapCalculateEntropy function is not defined')) + } + }, - dapGetToken: function(config, identity, ttl) { + dapGetTokenFromLocalStorage: function(ttl) { let now = Math.round(Date.now() / 1000.0); // in seconds - let storageName = 'async_dap_token'; let token = null; - - if (ttl == 0) { - localStorage.removeItem(storageName); + let item = JSON.parse(storage.getDataFromLocalStorage(DAP_TOKEN)); + if (item) { + if (now < item.expires_at) { + token = item.token; + } } + return token; + }, - let item = JSON.parse(localStorage.getItem(storageName)); - if (item == null) { - item = { - expires_at: now - 1, - token: null - }; - } else { - token = item.token; - } - - if (now > item.expires_at) { - dapUtils.dapLog('Token missing or expired, fetching a new one...'); - // Trigger a refresh - let configAsync = {...config}; - dapUtils.dapTokenize(configAsync, identity, - function(token, status, xhr) { - item.expires_at = now + ttl; - item.token = token; - localStorage.setItem(storageName, JSON.stringify(item)); - dapUtils.dapLog('Successfully updated and stored token; expires in ' + ttl + ' seconds'); - let deviceId100 = xhr.getResponseHeader('Akamai-DAP-100'); - if (deviceId100 != null) { - localStorage.setItem('dap_deviceId100', deviceId100); - dapUtils.dapLog('Successfully stored DAP 100 Device ID: ' + deviceId100); + dapRefreshToken: function(config, refreshMembership, onDone) { + dapUtils.dapLog('Token missing or expired, fetching a new one...'); + // Trigger a refresh + let now = Math.round(Date.now() / 1000.0); // in seconds + let item = {} + let configAsync = {...config}; + dapUtils.dapTokenize(configAsync, config.identity, onDone, + function(token, status, xhr, onDone) { + item.expires_at = now + DAP_DEFAULT_TOKEN_TTL; + let exp = dapUtils.dapExtractExpiryFromToken(token) + if (typeof exp == 'number') { + item.expires_at = exp - 10; + } + item.token = token; + storage.setDataInLocalStorage(DAP_TOKEN, JSON.stringify(item)); + dapUtils.dapLog('Successfully updated and stored token; expires at ' + item.expires_at); + let dapSSID = xhr.getResponseHeader('Akamai-DAP-SS-ID'); + if (dapSSID) { + storage.setDataInLocalStorage(DAP_SS_ID, JSON.stringify(dapSSID)); + } + let deviceId100 = xhr.getResponseHeader('Akamai-DAP-100'); + if (deviceId100 != null) { + storage.setDataInLocalStorage('dap_deviceId100', deviceId100); + dapUtils.dapLog('Successfully stored DAP 100 Device ID: ' + deviceId100); + } + if (refreshMembership) { + if (config.segtax == 504) { + dapUtils.dapRefreshEncryptedMembership(config, token, onDone); + } else { + dapUtils.dapRefreshMembership(config, token, onDone); } - }, - function(xhr, status, error) { - logError('ERROR(' + error + '): failed to retrieve token! ' + status); } - ); - } - - return token; + }, + function(xhr, status, error, onDone) { + logError('ERROR(' + error + '): failed to retrieve token! ' + status); + onDone() + } + ); }, - dapGetMembership: function(config, token) { + dapGetMembershipFromLocalStorage: function() { let now = Math.round(Date.now() / 1000.0); // in seconds - let storageName = 'async_dap_membership'; - let maxTtl = 3600; // if the cached membership is older than this, return null let membership = null; - let item = JSON.parse(localStorage.getItem(storageName)); - if (item == null || (now - item.expires_at) > maxTtl) { - item = { - expires_at: now - 1, - said: null, - cohorts: null, - attributes: null - }; - } else { - membership = { - said: item.said, - cohorts: item.cohorts, - attributes: null - }; + let item = JSON.parse(storage.getDataFromLocalStorage(DAP_MEMBERSHIP)); + if (item) { + if (now < item.expires_at) { + membership = { + said: item.said, + cohorts: item.cohorts, + attributes: null + }; + } } + return membership; + }, - // Always refresh the cached membership. + dapRefreshMembership: function(config, token, onDone) { + let now = Math.round(Date.now() / 1000.0); // in seconds + let item = {} let configAsync = {...config}; - dapUtils.dapMembership(configAsync, token, - function(membership, status, xhr) { - item.expires_at = now + maxTtl; + dapUtils.dapMembership(configAsync, token, onDone, + function(membership, status, xhr, onDone) { + item.expires_at = now + DAP_DEFAULT_TOKEN_TTL; + let exp = dapUtils.dapExtractExpiryFromToken(membership.said) + if (typeof exp == 'number') { + item.expires_at = exp - 10; + } item.said = membership.said; item.cohorts = membership.cohorts; - localStorage.setItem(storageName, JSON.stringify(item)); + storage.setDataInLocalStorage(DAP_MEMBERSHIP, JSON.stringify(item)); dapUtils.dapLog('Successfully updated and stored membership:'); dapUtils.dapLog(item); + + let data = dapUtils.dapGetRtdObj(item, config.segtax) + dapUtils.checkAndAddRealtimeData(data, config.segtax); + onDone(); }, - function(xhr, status, error) { + function(xhr, status, error, onDone) { logError('ERROR(' + error + '): failed to retrieve membership! ' + status); + if (status == 403 && dapRetryTokenize < DAP_MAX_RETRY_TOKENIZE) { + dapRetryTokenize++; + dapUtils.dapRefreshToken(config, true, onDone); + } else { + onDone(); + } } ); + }, - return membership; + dapGetEncryptedMembershipFromLocalStorage: function() { + let now = Math.round(Date.now() / 1000.0); // in seconds + let encMembership = null; + let item = JSON.parse(storage.getDataFromLocalStorage(DAP_ENCRYPTED_MEMBERSHIP)); + if (item) { + if (now < item.expires_at) { + encMembership = { + encryptedSegments: item.encryptedSegments + }; + } + } + return encMembership; + }, + + dapRefreshEncryptedMembership: function(config, token, onDone) { + let now = Math.round(Date.now() / 1000.0); // in seconds + let item = {}; + let configAsync = {...config}; + dapUtils.dapEncryptedMembership(configAsync, token, onDone, + function(encToken, status, xhr, onDone) { + item.expires_at = now + DAP_DEFAULT_TOKEN_TTL; + let exp = dapUtils.dapExtractExpiryFromToken(encToken) + if (typeof exp == 'number') { + item.expires_at = exp - 10; + } + item.encryptedSegments = encToken; + storage.setDataInLocalStorage(DAP_ENCRYPTED_MEMBERSHIP, JSON.stringify(item)); + dapUtils.dapLog('Successfully updated and stored encrypted membership:'); + dapUtils.dapLog(item); + + let encData = dapUtils.dapGetEncryptedRtdObj(item, config.segtax); + dapUtils.checkAndAddRealtimeData(encData, config.segtax); + onDone(); + }, + function(xhr, status, error, onDone) { + logError('ERROR(' + error + '): failed to retrieve encrypted membership! ' + status); + if (status == 403 && dapRetryTokenize < DAP_MAX_RETRY_TOKENIZE) { + dapRetryTokenize++; + dapUtils.dapRefreshToken(config, true, onDone); + } else { + onDone(); + } + } + ); + }, + + /** + * DESCRIPTION + * Extract expiry value from a token + */ + dapExtractExpiryFromToken: function(token) { + let exp = null; + if (token) { + const tokenArray = token.split('..'); + if (tokenArray && tokenArray.length > 0) { + let decode = atob(tokenArray[0]) + let header = JSON.parse(decode.replace(/"/g, '"')); + exp = header.exp; + } + } + return exp }, /** * DESCRIPTION * * Convert a DAP membership response to an OpenRTB2 segment object suitable - * for insertion into user.data.segment or site.data.segment. + * for insertion into user.data.segment or site.data.segment and add it to the rtd obj. */ - dapMembershipToRtbSegment: function(membership, config) { + dapGetRtdObj: function(membership, segtax) { let segment = { name: 'dap.akamai.com', ext: { - 'segtax': config.segtax + 'segtax': segtax }, segment: [] }; @@ -234,7 +365,83 @@ export const dapUtils = { segment.segment.push({ id: i }); } } - return segment; + let data = { + rtd: { + ortb2: { + user: { + data: [ + segment + ] + }, + site: { + ext: { + data: { + dapSAID: membership.said + } + } + } + } + } + }; + return data; + }, + + /** + * DESCRIPTION + * + * Convert a DAP membership response to an OpenRTB2 segment object suitable + * for insertion into user.data.segment or site.data.segment and add it to the rtd obj. + */ + dapGetEncryptedRtdObj: function(encToken, segtax) { + let segment = { + name: 'dap.akamai.com', + ext: { + 'segtax': segtax + }, + segment: [] + }; + if (encToken != null) { + segment.segment.push({ id: encToken.encryptedSegments }); + } + let encData = { + rtd: { + ortb2: { + user: { + data: [ + segment + ] + } + } + } + }; + return encData; + }, + + checkAndAddRealtimeData: function(data, segtax) { + if (data.rtd) { + if (segtax == 504 && dapUtils.checkIfSegmentsAlreadyExist(data.rtd, 504)) { + logMessage('DEBUG(handleInit): rtb Object already added'); + } else { + addRealTimeData(data.rtd); + } + logInfo('DEBUG(getRealTimeData) - 1'); + } + }, + + checkIfSegmentsAlreadyExist: function(rtd, segtax) { + let segmentsExist = false + let ortb2 = config.getConfig('ortb2') || {}; + if (ortb2.user && ortb2.user.data && ortb2.user.data.length > 0) { + for (let i = 0; i < ortb2.user.data.length; i++) { + let element = ortb2.user.data[i] + if (element.ext && element.ext.segtax == segtax) { + segmentsExist = true + logMessage('DEBUG(checkIfSegmentsAlreadyExist): rtb Object already added: ', ortb2.user.data); + break; + } + } + } + return segmentsExist }, dapLog: function(args) { @@ -248,6 +455,35 @@ export const dapUtils = { logInfo('%cDAP Client', css, args); }, + isValidHttpsUrl: function(urlString) { + let url; + try { + url = new URL(urlString); + } catch (_) { + return false; + } + return url.protocol === 'https:'; + }, + + checkConsent: function(userConsent) { + let consent = true; + + if (userConsent && userConsent.gdpr && userConsent.gdpr.gdprApplies) { + const gdpr = userConsent.gdpr; + const hasGdpr = (gdpr && typeof gdpr.gdprApplies === 'boolean' && gdpr.gdprApplies) ? 1 : 0; + const gdprConsentString = hasGdpr ? gdpr.consentString : ''; + if (hasGdpr && (!gdprConsentString || gdprConsentString === '')) { + logError('akamaiDapRtd submodule requires consent string to call API'); + consent = false; + } + } else if (userConsent && userConsent.usp) { + const usp = userConsent.usp; + consent = usp[1] !== 'N' && usp[2] !== 'Y'; + } + + return consent; + }, + /******************************************************************************* * * V2 (And Beyond) API @@ -293,23 +529,23 @@ export const dapUtils = { * function( response, status, xhr } { token = response; }, * function( xhr, status, error ) { ; } // handle error */ - dapTokenize: function(config, identity, onSuccess = null, onError = null) { + dapTokenize: function(config, identity, onDone, onSuccess = null, onError = null) { if (onError == null) { - onError = function(xhr, status, error) {}; + onError = function(xhr, status, error, onDone) {}; } if (config == null || typeof (config) == typeof (undefined)) { - onError(null, 'Invalid config object', 'ClientError'); + onError(null, 'Invalid config object', 'ClientError', onDone); return; } if (typeof (config.domain) != 'string') { - onError(null, 'Invalid config.domain: must be a string', 'ClientError'); + onError(null, 'Invalid config.domain: must be a string', 'ClientError', onDone); return; } if (config.domain.length <= 0) { - onError(null, 'Invalid config.domain: must have non-zero length', 'ClientError'); + onError(null, 'Invalid config.domain: must have non-zero length', 'ClientError', onDone); return; } @@ -318,22 +554,22 @@ export const dapUtils = { } if (typeof (config.api_version) != 'string') { - onError(null, "Invalid api_version: must be a string like 'x1', etc.", 'ClientError'); + onError(null, "Invalid api_version: must be a string like 'x1', etc.", 'ClientError', onDone); return; } if (!(('api_hostname') in config) || typeof (config.api_hostname) != 'string' || config.api_hostname.length == 0) { - onError(null, 'Invalid api_hostname: must be a non-empty string', 'ClientError'); + onError(null, 'Invalid api_hostname: must be a non-empty string', 'ClientError', onDone); return; } if (identity == null || typeof (identity) == typeof (undefined)) { - onError(null, 'Invalid identity object', 'ClientError'); + onError(null, 'Invalid identity object', 'ClientError', onDone); return; } if (!('type' in identity) || typeof (identity.type) != 'string' || identity.type.length <= 0) { - onError(null, "Identity must contain a valid 'type' field", 'ClientError'); + onError(null, "Identity must contain a valid 'type' field", 'ClientError', onDone); return; } @@ -348,6 +584,11 @@ export const dapUtils = { apiParams.attributes = identity.attributes; } + let entropyDict = JSON.parse(storage.getDataFromLocalStorage(DAP_CLIENT_ENTROPY)); + if (entropyDict && entropyDict.entropy) { + apiParams.entropy = entropyDict.entropy; + } + let method; let body; let path; @@ -359,10 +600,16 @@ export const dapUtils = { body = JSON.stringify(apiParams); break; default: - onError(null, 'Invalid api_version: ' + config.api_version, 'ClientError'); + onError(null, 'Invalid api_version: ' + config.api_version, 'ClientError', onDone); return; } + let customHeaders = {'Content-Type': 'application/json'}; + let dapSSID = JSON.parse(storage.getDataFromLocalStorage(DAP_SS_ID)); + if (dapSSID) { + customHeaders['Akamai-DAP-SS-ID'] = dapSSID; + } + let url = 'https://' + config.api_hostname + path; let cb = { success: (response, request) => { @@ -373,19 +620,16 @@ export const dapUtils = { token = request.getResponseHeader('Akamai-DAP-Token'); break; } - onSuccess(token, request.status, request); + onSuccess(token, request.status, request, onDone); }, error: (request, error) => { - onError(request, request.statusText, error); + onError(request, request.statusText, error, onDone); } }; ajax(url, cb, body, { method: method, - customHeaders: { - 'Content-Type': 'application/json', - 'Pragma': 'akamai-x-cache-on' - } + customHeaders: customHeaders }); }, @@ -411,7 +655,7 @@ export const dapUtils = { * api_hostname: 'api.dap.akadns.net', * }; * - * // token from dap_x1_tokenize + * // token from dap_tokenize * * dapMembership( config, token, * function( membership, status, xhr ) { @@ -422,13 +666,13 @@ export const dapUtils = { * } ); * */ - dapMembership: function(config, token, onSuccess = null, onError = null) { + dapMembership: function(config, token, onDone, onSuccess = null, onError = null) { if (onError == null) { - onError = function(xhr, status, error) {}; + onError = function(xhr, status, error, onDone) {}; } if (config == null || typeof (config) == typeof (undefined)) { - onError(null, 'Invalid config object', 'ClientError'); + onError(null, 'Invalid config object', 'ClientError', onDone); return; } @@ -437,32 +681,32 @@ export const dapUtils = { } if (typeof (config.api_version) != 'string') { - onError(null, "Invalid api_version: must be a string like 'x1', etc.", 'ClientError'); + onError(null, "Invalid api_version: must be a string like 'x1', etc.", 'ClientError', onDone); return; } if (!(('api_hostname') in config) || typeof (config.api_hostname) != 'string' || config.api_hostname.length == 0) { - onError(null, 'Invalid api_hostname: must be a non-empty string', 'ClientError'); + onError(null, 'Invalid api_hostname: must be a non-empty string', 'ClientError', onDone); return; } if (token == null || typeof (token) != 'string') { - onError(null, 'Invalid token: must be a non-null string', 'ClientError'); + onError(null, 'Invalid token: must be a non-null string', 'ClientError', onDone); return; } let path = '/data-activation/' + - config.api_version + - '/token/' + token + - '/membership'; + config.api_version + + '/token/' + token + + '/membership'; let url = 'https://' + config.api_hostname + path; let cb = { success: (response, request) => { - onSuccess(JSON.parse(response), request.status, request); + onSuccess(JSON.parse(response), request.status, request, onDone); }, error: (error, request) => { - onError(request, request.status, error); + onError(request, request.status, error, onDone); } }; @@ -470,5 +714,91 @@ export const dapUtils = { method: 'GET', customHeaders: {} }); + }, + + /** + * SYNOPSIS + * + * dapEncryptedMembership( config, token, onSuccess, onError ); + * + * DESCRIPTION + * + * Return the audience segment membership along with a new Secure Advertising + * ID for this token in encrypted format. + * + * PARAMETERS + * + * config: an array of system configuration parameters + * + * token: the token previously returned from the tokenize API + * + * EXAMPLE + * + * config = { + * api_hostname: 'api.dap.akadns.net', + * }; + * + * // token from dap_tokenize + * + * dapEncryptedMembership( config, token, + * function( membership, status, xhr ) { + * // Run auction with membership.segments and membership.said after decryption + * }, + * function( xhr, status, error ) { + * // error + * } ); + * + */ + dapEncryptedMembership: function(config, token, onDone, onSuccess = null, onError = null) { + if (onError == null) { + onError = function(xhr, status, error, onDone) {}; + } + + if (config == null || typeof (config) == typeof (undefined)) { + onError(null, 'Invalid config object', 'ClientError', onDone); + return; + } + + if (!('api_version' in config) || (typeof (config.api_version) == 'string' && config.api_version.length == 0)) { + config.api_version = 'x1'; + } + + if (typeof (config.api_version) != 'string') { + onError(null, "Invalid api_version: must be a string like 'x1', etc.", 'ClientError', onDone); + return; + } + + if (!(('api_hostname') in config) || typeof (config.api_hostname) != 'string' || config.api_hostname.length == 0) { + onError(null, 'Invalid api_hostname: must be a non-empty string', 'ClientError', onDone); + return; + } + + if (token == null || typeof (token) != 'string') { + onError(null, 'Invalid token: must be a non-null string', 'ClientError', onDone); + return; + } + let path = '/data-activation/' + + config.api_version + + '/token/' + token + + '/membership/encrypt'; + + let url = 'https://' + config.api_hostname + path; + + let cb = { + success: (response, request) => { + let encToken = request.getResponseHeader('Akamai-DAP-Token'); + onSuccess(encToken, request.status, request, onDone); + }, + error: (error, request) => { + onError(request, request.status, error, onDone); + } + }; + ajax(url, cb, undefined, { + method: 'GET', + customHeaders: { + 'Content-Type': 'application/json', + 'Pragma': 'akamai-x-get-extracted-values' + } + }); } } diff --git a/modules/akamaiDapRtdProvider.md b/modules/akamaiDapRtdProvider.md index ade11b88602..5e3b93cc5fc 100644 --- a/modules/akamaiDapRtdProvider.md +++ b/modules/akamaiDapRtdProvider.md @@ -17,6 +17,7 @@ ``` pbjs.setConfig({ realTimeData: { + auctionDelay: 2000, dataProviders: [ { name: "dap", @@ -25,9 +26,10 @@ apiHostname: '', apiVersion: "x1", domain: 'your-domain.com', - identityType: 'email' | 'mobile' | ... | 'dap-signature:1.0.0', - segtax: , - tokenTtl: 5, + identityType: 'email' | 'mobile' | ... | 'dap-signature:1.3.0', + segtax: 504, + dapFpUrl: 'https://dap-dist.akamaized.net/dapfingerprinting.js', + dapFpTimeout: 1500 // Maximum time for dapFP to run } } ] diff --git a/src/adloader.js b/src/adloader.js index db128c6d7ba..c73f83f300c 100644 --- a/src/adloader.js +++ b/src/adloader.js @@ -10,7 +10,8 @@ const _approvedLoadExternalJSList = [ 'adagio', 'browsi', 'brandmetrics', - 'justtag' + 'justtag', + 'akamaidap' ] /** diff --git a/test/spec/modules/akamaiDapRtdProvider_spec.js b/test/spec/modules/akamaiDapRtdProvider_spec.js index b350c2bb529..25688abd99e 100644 --- a/test/spec/modules/akamaiDapRtdProvider_spec.js +++ b/test/spec/modules/akamaiDapRtdProvider_spec.js @@ -1,13 +1,14 @@ import {config} from 'src/config.js'; -import {SEGMENTS_STORAGE_KEY, TOKEN_STORAGE_KEY, dapUtils, addRealTimeData, getRealTimeData, akamaiDapRtdSubmodule, storage} from 'modules/akamaiDapRtdProvider.js'; +import { + dapUtils, + generateRealTimeData, + akamaiDapRtdSubmodule, + storage, DAP_MAX_RETRY_TOKENIZE, DAP_SS_ID, DAP_TOKEN, DAP_MEMBERSHIP, DAP_ENCRYPTED_MEMBERSHIP, +} from 'modules/akamaiDapRtdProvider.js'; import {server} from 'test/mocks/xhr.js'; -import logMessage from 'src/utils.js' const responseHeader = {'Content-Type': 'application/json'}; describe('akamaiDapRtdProvider', function() { - let getDataFromLocalStorageStub; - let getDapTokenStub; - const testReqBidsConfigObj = { adUnits: [ { @@ -18,7 +19,21 @@ describe('akamaiDapRtdProvider', function() { const onDone = function() { return true }; - const onSuccess = function() { return ('request', 200, 'success') }; + const sampleGdprConsentConfig = { + 'gdpr': { + 'consentString': null, + 'vendorData': {}, + 'gdprApplies': true + } + }; + + const sampleUspConsentConfig = { + 'usp': '1YYY' + }; + + const sampleIdentity = { + type: 'dap-signature:1.0.0' + }; const cmoduleConfig = { 'name': 'dap', @@ -28,8 +43,19 @@ describe('akamaiDapRtdProvider', function() { 'apiVersion': 'x1', 'domain': 'prebid.org', 'identityType': 'dap-signature:1.0.0', - 'segtax': 503, - 'tokenTtl': 5 + 'segtax': 503 + } + } + + const emoduleConfig = { + 'name': 'dap', + 'waitForIt': true, + 'params': { + 'apiHostname': 'prebid.dap.akadns.net', + 'apiVersion': 'x1', + 'domain': 'prebid.org', + 'identityType': 'dap-signature:1.0.0', + 'segtax': 504 } } @@ -37,81 +63,119 @@ describe('akamaiDapRtdProvider', function() { 'api_hostname': 'prebid.dap.akadns.net', 'api_version': 'x1', 'domain': 'prebid.org', - 'segtax': 503 + 'segtax': 503, + 'identity': sampleIdentity } - const sampleIdentity = { - type: 'dap-signature:1.0.0' + + const esampleConfig = { + 'api_hostname': 'prebid.dap.akadns.net', + 'api_version': 'x1', + 'domain': 'prebid.org', + 'segtax': 504, + 'identity': sampleIdentity + } + let cacheExpiry = Math.round(Date.now() / 1000.0) + 300; // in seconds + const sampleCachedToken = {'expires_at': cacheExpiry, 'token': 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIn0..6buzBd2BjtgoyaNbHN8YnQ.l38avCfm3sYNy798-ETYOugz0cOx1cCkjACkAhYszxzrZ0sUJ0AiF-NdDXVTiTyp2Ih3vCWKzS0rKJ8lbS1zhyEVWVu91QwtwseM2fBbwA5ggAgBEo5wV-IXqDLPxVnxsPF0D3hP6cNCiH9Q2c-vULfsLhMhG5zvvZDPBbn4hUY5fKB8LoCBTF9rbuuWGYK1nramnb4AlS5UK82wBsHQea1Ou_Kp5wWCMNZ6TZk5qKIuRBfPIAhQblWvHECaHXkg1wyoM9VASs_yNhne7RR-qkwzbFiPFiMJibNOt9hF3_vPDJO5-06ZBjRTP1BllYGWxI-uQX6InzN18Wtun2WHqg.63sH0SNlIRcsK57v0pMujfB_nhU8Y5CuQbsHqH5MGoM'}; + const cachedEncryptedMembership = {'expires_at': cacheExpiry, 'encryptedSegments': 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoic29tZXNlY3JldGludmF1bHQifQ..IvnIUQDqWBVYIS0gbcE9bw.Z4NZGvtogWaWlGH4e-GdYKe_PUc15M2x3Bj85rMWsN1A17mIxQIMOfg2hsQ2tgieLu5LggWPmsFu1Wbph6P0k3kOu1dVReoIhOHzxw50rP0DLHKaEZ5mLMJ7Lcosvwh4miIfFuCHlsX7J0sFgOTAp0zGo1S_UsHLtev1JflhjoSB0AoX95ALbAnyctirPuLJM8gZ1vXTiZ01jpvucGyR1lM4cWjPOeD8jPtgwaPGgSRZXE-3X2Cqy7z4Giam5Uqu74LPWTBuKtUQTGyAXA5QJoP7xwTbsU4O1f69lu3fWNqC92GijeTH1A4Zd_C-WXxWuQlDEURjlkWQoaqTHka2OqlnwukEQIf_v0r5KQQX64CTLhEUH91jeD0-E9ClcIP7pwOLxxqiKoaBmx8Mrnm_6Agj5DtTA1rusy3AL63sI_rsUxrmLrVt0Wft4aCfRkW8QpQxu8clFdOmce0NNCGeBCyCPVw9d9izrILlXJ6rItU2cpFrcbz8uw2otamF5eOFCOY3IzHedWVNNuKHFIUVC_xYSlsYvQ8f2QIP1eiMbmukcuPzmTzjw1h1_7IKaj-jJkXrnrY-TdDgX_4-_Z3rmbpXK2yTR7dBrsg-ubqFbgbKic1b4zlQEO_LbBlgPl3DYdWEuJ8CY2NUt1GfpATQGsufS2FTY1YGw_gkPe3q04l_cgLafDoxHvHh_t_0ZgPjciW82gThB_kN4RP7Mc3krVcXl_P6N1VbV07xyx0hCyVsrrxbLslI8q9wYDiLGci7mNmByM5j7SXV9jPwwPkHtn0HfMJlw2PFbIDPjgG3h7sOyLcBIJTTvuUIgpHPIkRWLIl_4FlIucXbJ7orW2nt5BWleBVHgumzGcnl9ZNcZb3W-dsdYPSOmuj0CY28MRTP2oJ1rzLInbDDpIRffJBtR7SS4nYyy7Vi09PtBigod5YNz1Q0WDSJxr8zeH_aKFaXInw7Bfo_U0IAcLiRgcT0ogsMLeQRjRFy27mr4XNJv3NtHhbdjDAwF2aClCktXyXbQaVdsPH2W71v6m2Q9rB5GQWOktw2s5f-4N1-_EBPGq6TgjF-aJZP22MJVwp1pimT50DfOzoeEqDwi862NNwNNoHmcObH0ZfwAXlhRxsgupNBe20-MNNABj2Phlfv4DUrtQbMdfCnNiypzNCmoTb7G7c_o5_JUwoV_GVkwUtvmi_IUm05P4GeMASSUw8zDKVRAj9h31C2cabM8RjMHGhkbCWpUP2pcz9zlJ7Y76Dh3RLnctfTw7DG9U4w4UlaxNZOgLUiSrGwfyapuSiuGUpuOJkBBLiHmEqAGI5C8oJpcVRccNlHxJAYowgXyFopD5Fr-FkXmv8KMkS0h5C9F6KihmDt5sqDD0qnjM0hHJgq01l7wjVnhEmPpyD-6auFQ-xDnbh1uBOJ_0gCVbRad--FSa5p-dXenggegRxOvZXJ0iAtM6Fal5Og-RCjexIHa9WhVbXhQBJpkSTWwAajZJ64eQ.yih49XB51wE-Xob7COT9OYqBrzBmIMVCQbLFx2UdzkI'}; + const cachedMembership = {'expires_at': cacheExpiry, 'said': 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIn0..QwvU5h0NVJYaJbs5EqWCKA.XNaJHSlnsH8P-yBIr3gIEqavLONWDIFyj7QCHFwJVkwXH_EYkxrk0_26b0uMPzfJp5URnqxKZusMH9DzEJsmj8EMrKQv1y3IYYMsW5_0BdP5bcAWfG6fzOqtMOwLiYRkYiQOqn1ZVGzhovheHWEmNr2_oCY0LvAr3iN1eG_K-l-bBKvBWnwvuuGKquUfCqO8NMMq6wtkecEXM9blqFRZ7oNYmW2aIG7qcHUsrUW7HMr9Ev2Ik0sIeEUsOYrgf_X_VA64RgKSTRugS9FupMv1p54JkHokwduF9pOFmW8QLQi8itFogKGbbgvOTNnmahxQUX5FcrjjYLqHwKqC8htLdlHnO5LWU9l4A7vLXrRurvoSnh0cAJy0GsdoyEwTqR9bwVFHoPquxlJjQ4buEd7PIxpBj9Qg9oOPH3b2upbMTu5CQ9oj526eXPhP5G54nwGklm2AZ3Vggd7jCQJn45Jjiq0iIfsXAtpqS2BssCLBN8WhmUTnStK8m5sux6WUBdrpDESQjPj-EEHVS-DB5rA7icRUh6EzRxzen2rndvHvnwVhSG_l6cwPYuJ0HE0KBmYHOoqNpKwzoGiKFHrf4ReA06iWB3V2TEGJucGujhtQ9_18WwHCeJ1XtQiiO1eqa3tp5MwAbFXawVFl3FFOBgadrPyvGmkmUJ6FCLU2MSwHiYZmANMnJsokFX_6DwoAgO3U_QnvEHIVSvefc7ReeJ8fBDdmrH3LtuLrUpXsvLvEIMQdWQ_SXhjKIi7tOODR8CfrhUcdIjsp3PZs1DpuOcDB6YJKbGnKZTluLUJi3TyHgyi-DHXdTm-jSE5i_DYJGW-t2Gf23FoQhexv4q7gdrfsKfcRJNrZLp6Gd6jl4zHhUtY.nprKBsy9taQBk6dCPbA7BFF0CiGhQOEF_MazZ2bedqk', 'cohorts': ['9', '11', '13']}; + const rtdUserObj = { + name: 'www.dataprovider3.com', + ext: { + taxonomyname: 'iab_audience_taxonomy' + }, + segment: [ + { + id: '1918' + }, + { + id: '1939' + } + ] + }; + + const encRtdUserObj = { + name: 'www.dataprovider3.com', + ext: { + segtax: 504, + taxonomyname: 'iab_audience_taxonomy' + }, + segment: [] + }; + + const cachedRtd = { + rtd: { + ortb2: { + user: { + data: [rtdUserObj] + } + } + } + }; + + let membership = { + said: cachedMembership.said, + cohorts: cachedMembership.cohorts, + attributes: null + }; + let encMembership = { + encryptedSegments: cachedEncryptedMembership.encryptedSegments + }; + encRtdUserObj.segment.push({ id: encMembership.encryptedSegments }); + const cachedEncRtd = { + rtd: { + ortb2: { + user: { + data: [encRtdUserObj] + } + } + } }; beforeEach(function() { config.resetConfig(); - getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage') + storage.removeDataFromLocalStorage(DAP_TOKEN); + storage.removeDataFromLocalStorage(DAP_MEMBERSHIP); + storage.removeDataFromLocalStorage(DAP_ENCRYPTED_MEMBERSHIP); + storage.removeDataFromLocalStorage(DAP_SS_ID); }); afterEach(function () { - getDataFromLocalStorageStub.restore(); }); describe('akamaiDapRtdSubmodule', function() { it('successfully instantiates', function () { - expect(akamaiDapRtdSubmodule.init()).to.equal(true); + expect(akamaiDapRtdSubmodule.init()).to.equal(true); }); }); describe('Get Real-Time Data', function() { it('gets rtd from local storage cache', function() { - const rtdConfig = { - params: { - segmentCache: true - } - }; - const bidConfig = {}; - - const rtdUserObj1 = { - name: 'www.dataprovider3.com', - ext: { - taxonomyname: 'iab_audience_taxonomy' - }, - segment: [ - { - id: '1918' - }, - { - id: '1939' - } - ] - }; - - const cachedRtd = { - rtd: { - ortb2: { - user: { - data: [rtdUserObj1] - } - } - } - }; - - getDataFromLocalStorageStub.withArgs(SEGMENTS_STORAGE_KEY).returns(JSON.stringify(cachedRtd)); + storage.setDataInLocalStorage(DAP_TOKEN, JSON.stringify(sampleCachedToken)); + let dapGetMembershipFromLocalStorageStub = sinon.stub(dapUtils, 'dapGetMembershipFromLocalStorage').returns(membership) + let dapGetRtdObjStub = sinon.stub(dapUtils, 'dapGetRtdObj').returns(cachedRtd) + let dapGetEncryptedMembershipFromLocalStorageStub = sinon.stub(dapUtils, 'dapGetEncryptedMembershipFromLocalStorage').returns(encMembership) + let dapGetEncryptedRtdObjStub = sinon.stub(dapUtils, 'dapGetEncryptedRtdObj').returns(cachedEncRtd) expect(config.getConfig().ortb2).to.be.undefined; - getRealTimeData(bidConfig, () => {}, rtdConfig, {}); - expect(config.getConfig().ortb2.user.data).to.deep.include.members([rtdUserObj1]); - }); - - it('should initalise and return with config', function () { - expect(getRealTimeData(testReqBidsConfigObj, onDone, cmoduleConfig)).to.equal(undefined) + generateRealTimeData(bidConfig, () => {}, emoduleConfig, {}); + expect(config.getConfig().ortb2.user.data).to.deep.include.members([encRtdUserObj]); + generateRealTimeData(bidConfig, () => {}, cmoduleConfig, {}); + expect(config.getConfig().ortb2.user.data).to.deep.include.members([rtdUserObj]); + dapGetRtdObjStub.restore() + dapGetMembershipFromLocalStorageStub.restore() + dapGetEncryptedRtdObjStub.restore() + dapGetEncryptedMembershipFromLocalStorageStub.restore() }); }); describe('dapTokenize', function () { it('dapTokenize error callback', function () { let configAsync = JSON.parse(JSON.stringify(sampleConfig)); - let submoduleCallback = dapUtils.dapTokenize(configAsync, sampleIdentity, - function(token, status, xhr) { + let submoduleCallback = dapUtils.dapTokenize(configAsync, sampleIdentity, onDone, + function(token, status, xhr, onDone) { }, - function(xhr, status, error) { + function(xhr, status, error, onDone) { } ); let request = server.requests[0]; @@ -121,10 +185,10 @@ describe('akamaiDapRtdProvider', function() { it('dapTokenize success callback', function () { let configAsync = JSON.parse(JSON.stringify(sampleConfig)); - let submoduleCallback = dapUtils.dapTokenize(configAsync, sampleIdentity, - function(token, status, xhr) { + let submoduleCallback = dapUtils.dapTokenize(configAsync, sampleIdentity, onDone, + function(token, status, xhr, onDone) { }, - function(xhr, status, error) { + function(xhr, status, error, onDone) { } ); let request = server.requests[0]; @@ -135,40 +199,54 @@ describe('akamaiDapRtdProvider', function() { describe('dapTokenize and dapMembership incorrect params', function () { it('Onerror and config are null', function () { - expect(dapUtils.dapTokenize(null, 'identity', null, null)).to.be.equal(undefined); - expect(dapUtils.dapMembership(null, 'identity', null, null)).to.be.equal(undefined); + expect(dapUtils.dapTokenize(null, 'identity', onDone, null, null)).to.be.equal(undefined); + expect(dapUtils.dapMembership(null, 'identity', onDone, null, null)).to.be.equal(undefined); + expect(dapUtils.dapEncryptedMembership(null, 'identity', onDone, null, null)).to.be.equal(undefined); const config = { 'api_hostname': 'prebid.dap.akadns.net', 'api_version': 1, 'domain': '', 'segtax': 503 }; + const encConfig = { + 'api_hostname': 'prebid.dap.akadns.net', + 'api_version': 1, + 'domain': '', + 'segtax': 504 + }; let identity = { type: 'dap-signature:1.0.0' }; - expect(dapUtils.dapTokenize(config, identity, null, null)).to.be.equal(undefined); - expect(dapUtils.dapMembership(config, 'token', null, null)).to.be.equal(undefined); + expect(dapUtils.dapTokenize(config, identity, onDone, null, null)).to.be.equal(undefined); + expect(dapUtils.dapMembership(config, 'token', onDone, null, null)).to.be.equal(undefined); + expect(dapUtils.dapEncryptedMembership(encConfig, 'token', onDone, null, null)).to.be.equal(undefined); }); + }); - it('dapGetToken success', function () { - let dapTokenizeStub = sinon.stub(dapUtils, 'dapTokenize').returns(onSuccess); - expect(dapUtils.dapGetToken(sampleConfig, 'token', - function(token, status, xhr) { - }, - function(xhr, status, error) { - } - )).to.be.equal(null); - dapTokenizeStub.restore(); + describe('Getting dapTokenize, dapMembership and dapEncryptedMembership from localstorage', function () { + it('dapGetTokenFromLocalStorage success', function () { + storage.setDataInLocalStorage(DAP_TOKEN, JSON.stringify(sampleCachedToken)); + expect(dapUtils.dapGetTokenFromLocalStorage(60)).to.be.equal(sampleCachedToken.token); + }); + + it('dapGetMembershipFromLocalStorage success', function () { + storage.setDataInLocalStorage(DAP_MEMBERSHIP, JSON.stringify(cachedMembership)); + expect(JSON.stringify(dapUtils.dapGetMembershipFromLocalStorage())).to.be.equal(JSON.stringify(membership)); + }); + + it('dapGetEncryptedMembershipFromLocalStorage success', function () { + storage.setDataInLocalStorage(DAP_ENCRYPTED_MEMBERSHIP, JSON.stringify(cachedEncryptedMembership)); + expect(JSON.stringify(dapUtils.dapGetEncryptedMembershipFromLocalStorage())).to.be.equal(JSON.stringify(encMembership)); }); }); describe('dapMembership', function () { it('dapMembership success callback', function () { let configAsync = JSON.parse(JSON.stringify(sampleConfig)); - let submoduleCallback = dapUtils.dapMembership(configAsync, 'token', - function(token, status, xhr) { + let submoduleCallback = dapUtils.dapMembership(configAsync, 'token', onDone, + function(token, status, xhr, onDone) { }, - function(xhr, status, error) { + function(xhr, status, error, onDone) { } ); let request = server.requests[0]; @@ -178,10 +256,38 @@ describe('akamaiDapRtdProvider', function() { it('dapMembership error callback', function () { let configAsync = JSON.parse(JSON.stringify(sampleConfig)); - let submoduleCallback = dapUtils.dapMembership(configAsync, 'token', - function(token, status, xhr) { + let submoduleCallback = dapUtils.dapMembership(configAsync, 'token', onDone, + function(token, status, xhr, onDone) { + }, + function(xhr, status, error, onDone) { + } + ); + let request = server.requests[0]; + request.respond(400, responseHeader, JSON.stringify('error')); + expect(submoduleCallback).to.equal(undefined); + }); + }); + + describe('dapEncMembership', function () { + it('dapEncMembership success callback', function () { + let configAsync = JSON.parse(JSON.stringify(esampleConfig)); + let submoduleCallback = dapUtils.dapEncryptedMembership(configAsync, 'token', onDone, + function(token, status, xhr, onDone) { + }, + function(xhr, status, error, onDone) { + } + ); + let request = server.requests[0]; + request.respond(200, responseHeader, JSON.stringify('success')); + expect(submoduleCallback).to.equal(undefined); + }); + + it('dapEncMembership error callback', function () { + let configAsync = JSON.parse(JSON.stringify(esampleConfig)); + let submoduleCallback = dapUtils.dapEncryptedMembership(configAsync, 'token', onDone, + function(token, status, xhr, onDone) { }, - function(xhr, status, error) { + function(xhr, status, error, onDone) { } ); let request = server.requests[0]; @@ -192,55 +298,243 @@ describe('akamaiDapRtdProvider', function() { describe('dapMembership', function () { it('should invoke the getDapToken and getDapMembership', function () { - let config = { - api_hostname: cmoduleConfig.params.apiHostname, - api_version: cmoduleConfig.params.apiVersion, - domain: cmoduleConfig.params.domain, - segtax: cmoduleConfig.params.segtax - }; - let identity = { - type: cmoduleConfig.params.identityType - }; - let membership = { said: 'item.said1', cohorts: 'item.cohorts', attributes: null }; - let getDapTokenStub = sinon.stub(dapUtils, 'dapGetToken').returns('token3'); - let getDapMembershipStub = sinon.stub(dapUtils, 'dapGetMembership').returns(membership); - let dapTokenizeStub = sinon.stub(dapUtils, 'dapTokenize').returns('response', 200, 'request'); - getRealTimeData(testReqBidsConfigObj, onDone, cmoduleConfig); - expect(getDapTokenStub.calledOnce).to.be.equal(true); + let getDapMembershipStub = sinon.stub(dapUtils, 'dapGetMembershipFromLocalStorage').returns(membership); + generateRealTimeData(testReqBidsConfigObj, onDone, cmoduleConfig); expect(getDapMembershipStub.calledOnce).to.be.equal(true); - getDapTokenStub.restore(); getDapMembershipStub.restore(); - dapTokenizeStub.restore(); }); }); - describe('dapMembershipToRtbSegment', function () { - it('dapMembershipToRtbSegment', function () { - let membership1 = { - said: 'item.said1', - cohorts: 'item.cohorts', - attributes: null + describe('dapEncMembership test', function () { + it('should invoke the getDapToken and getEncDapMembership', function () { + let encMembership = { + encryptedSegments: 'enc.seg', }; + + let getDapEncMembershipStub = sinon.stub(dapUtils, 'dapGetEncryptedMembershipFromLocalStorage').returns(encMembership); + generateRealTimeData(testReqBidsConfigObj, onDone, emoduleConfig); + expect(getDapEncMembershipStub.calledOnce).to.be.equal(true); + getDapEncMembershipStub.restore(); + }); + }); + + describe('dapGetRtdObj test', function () { + it('dapGetRtdObj', function () { const config = { apiHostname: 'prebid.dap.akadns.net', apiVersion: 'x1', domain: 'prebid.org', - tokenTtl: 5, segtax: 503 }; - let identity = { - type: 'dap-signature:1.0.0' - }; - - expect(dapUtils.dapGetMembership(config, 'token')).to.equal(null) + expect(dapUtils.dapRefreshMembership(config, 'token', onDone)).to.equal(undefined) const membership = {cohorts: ['1', '5', '7']} - expect(dapUtils.dapMembershipToRtbSegment(membership, config)).to.not.equal(undefined); + expect(dapUtils.dapGetRtdObj(membership, config.segtax)).to.not.equal(undefined); + }); + }); + + describe('checkAndAddRealtimeData test', function () { + it('add realtime data for segtax 503 and 504', function () { + dapUtils.checkAndAddRealtimeData(cachedEncRtd, 504); + dapUtils.checkAndAddRealtimeData(cachedEncRtd, 504); + expect(config.getConfig().ortb2.user.data).to.deep.include.members([encRtdUserObj]); + dapUtils.checkAndAddRealtimeData(cachedRtd, 503); + expect(config.getConfig().ortb2.user.data).to.deep.include.members([rtdUserObj]); + }); + }); + + describe('dapExtractExpiryFromToken test', function () { + it('test dapExtractExpiryFromToken function', function () { + let tokenWithoutExpiry = 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIn0..6buzBd2BjtgoyaNbHN8YnQ.l38avCfm3sYNy798-ETYOugz0cOx1cCkjACkAhYszxzrZ0sUJ0AiF-NdDXVTiTyp2Ih3vCWKzS0rKJ8lbS1zhyEVWVu91QwtwseM2fBbwA5ggAgBEo5wV-IXqDLPxVnxsPF0D3hP6cNCiH9Q2c-vULfsLhMhG5zvvZDPBbn4hUY5fKB8LoCBTF9rbuuWGYK1nramnb4AlS5UK82wBsHQea1Ou_Kp5wWCMNZ6TZk5qKIuRBfPIAhQblWvHECaHXkg1wyoM9VASs_yNhne7RR-qkwzbFiPFiMJibNOt9hF3_vPDJO5-06ZBjRTP1BllYGWxI-uQX6InzN18Wtun2WHqg.63sH0SNlIRcsK57v0pMujfB_nhU8Y5CuQbsHqH5MGoM' + expect(dapUtils.dapExtractExpiryFromToken(tokenWithoutExpiry)).to.equal(undefined); + let tokenWithExpiry = 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIiwiZXhwIjoxNjQzODMwMzY5fQ..hTbcSQgmmO0HUJJrQ5fRHw.7zjrQXNNVkb-GD0ZhIVhEPcWbyaDBilHTWv-bp1lFZ9mdkSC0QbcAvUbYteiTD7ya23GUwcL2WOW8WgRSHaWHOJe0B5NDqfdUGTzElWfu7fFodRxRgGmwG8Rq5xxteFKLLGHLf1mFYRJKDtjtgajGNUKIDfn9AEt-c5Qz4KU8VolG_KzrLROx-f6Z7MnoPTcwRCj0WjXD6j2D6RAZ80-mKTNIsMIELdj6xiabHcjDJ1WzwtwCZSE2y2nMs451pSYp8W-bFPfZmDDwrkjN4s9ASLlIXcXgxK-H0GsiEbckQOZ49zsIKyFtasBvZW8339rrXi1js-aBh99M7aS5w9DmXPpUDmppSPpwkeTfKiqF0cQiAUq8tpeEQrGDJuw3Qt2.XI8h9Xw-VZj_NOmKtV19wLM63S4snos7rzkoHf9FXCw' + expect(dapUtils.dapExtractExpiryFromToken(tokenWithExpiry)).to.equal(1643830369); + }); + }); + + describe('dapRefreshToken test', function () { + it('test dapRefreshToken success response', function () { + dapUtils.dapRefreshToken(sampleConfig, true, onDone) + let request = server.requests[0]; + responseHeader['Akamai-DAP-Token'] = sampleCachedToken.token; + request.respond(200, responseHeader, JSON.stringify(sampleCachedToken.token)); + expect(JSON.parse(storage.getDataFromLocalStorage(DAP_TOKEN)).token).to.be.equal(sampleCachedToken.token); + }); + + it('test dapRefreshToken success response with deviceid 100', function () { + dapUtils.dapRefreshToken(esampleConfig, true, onDone) + let request = server.requests[0]; + responseHeader['Akamai-DAP-100'] = sampleCachedToken.token; + request.respond(200, responseHeader, ''); + expect(storage.getDataFromLocalStorage('dap_deviceId100')).to.be.equal(sampleCachedToken.token); + }); + + it('test dapRefreshToken success response with exp claim', function () { + dapUtils.dapRefreshToken(sampleConfig, true, onDone) + let request = server.requests[0]; + let tokenWithExpiry = 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIiwiZXhwIjoxNjQzODMwMzY5fQ..hTbcSQgmmO0HUJJrQ5fRHw.7zjrQXNNVkb-GD0ZhIVhEPcWbyaDBilHTWv-bp1lFZ9mdkSC0QbcAvUbYteiTD7ya23GUwcL2WOW8WgRSHaWHOJe0B5NDqfdUGTzElWfu7fFodRxRgGmwG8Rq5xxteFKLLGHLf1mFYRJKDtjtgajGNUKIDfn9AEt-c5Qz4KU8VolG_KzrLROx-f6Z7MnoPTcwRCj0WjXD6j2D6RAZ80-mKTNIsMIELdj6xiabHcjDJ1WzwtwCZSE2y2nMs451pSYp8W-bFPfZmDDwrkjN4s9ASLlIXcXgxK-H0GsiEbckQOZ49zsIKyFtasBvZW8339rrXi1js-aBh99M7aS5w9DmXPpUDmppSPpwkeTfKiqF0cQiAUq8tpeEQrGDJuw3Qt2.XI8h9Xw-VZj_NOmKtV19wLM63S4snos7rzkoHf9FXCw' + responseHeader['Akamai-DAP-Token'] = tokenWithExpiry; + request.respond(200, responseHeader, JSON.stringify(tokenWithExpiry)); + expect(JSON.parse(storage.getDataFromLocalStorage(DAP_TOKEN)).expires_at).to.be.equal(1643830359); + }); + + it('test dapRefreshToken error response', function () { + storage.setDataInLocalStorage(DAP_TOKEN, JSON.stringify(sampleCachedToken)); + dapUtils.dapRefreshToken(sampleConfig, false, onDone) + let request = server.requests[0]; + request.respond(400, responseHeader, 'error'); + expect(JSON.parse(storage.getDataFromLocalStorage(DAP_TOKEN)).expires_at).to.be.equal(cacheExpiry);// Since the expiry is same, the token is not updated in the cache + }); + }); + + describe('dapRefreshMembership test', function () { + it('test dapRefreshMembership success response', function () { + let membership = {'cohorts': ['9', '11', '13'], 'said': 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIn0..17wnrhz6FbWx0Cf6LXpm1A.m9PKVCradk3CZokNKzVHzE06TOqiXYeijgxTQUiQy5Syx-yicnO8DyYX6zQ6rgPcNgUNRt4R4XE5MXuK0laUVQJr9yc9g3vUfQfw69OMYGW_vRlLMPzoNOhF2c4gSyfkRrLr7C0qgALmZO1D11sPflaCTNmO7pmZtRaCOB5buHoWcQhp1bUSJ09DNDb31dX3llimPwjNGSrUhyq_EZl4HopnnjxbM4qVNMY2G_43C_idlVOvbFoTxcDRATd-6MplJoIOIHQLDZEetpIOVcbEYN9gQ_ndBISITwuu5YEgs5C_WPHA25nm6e4BT5R-tawSA8yPyQAupqE8gk4ZWq_2-T0cqyTstIHrMQnZ_vysYN7h6bkzE-KeZRk7GMtySN87_fiu904hLD9QentGegamX6UAbVqQh7Htj7SnMHXkEenjxXAM5mRqQvNCTlw8k-9-VPXs-vTcKLYP8VFf8gMOmuYykgWac1gX-svyAg-24mo8cUbqcsj9relx4Qj5HiXUVyDMBZxK-mHZi-Xz6uv9GlggcsjE13DSszar-j2OetigpdibnJIxRZ-4ew3-vlvZ0Dul3j0LjeWURVBWYWfMjuZ193G7lwR3ohh_NzlNfwOPBK_SYurdAnLh7jJgTW-lVLjH2Dipmi9JwX9s03IQq9opexAn7hlM9oBI6x5asByH8JF8WwZ5GhzDjpDwpSmHPQNGFRSyrx_Sh2CPWNK6C1NJmLkyqAtJ5iw0_al7vPDQyZrKXaLTjBCUnbpJhUZ8dUKtWLzGPjzFXp10muoDIutd1NfyKxk1aWGhx5aerYuLdywv6cT_M8RZTi8924NGj5VA30V5OvEwLLyX93eDhntXZSCbkPHpAfiRZNGXrPY.GhCbWGQz11mIRD4uPKmoAuFXDH7hGnils54zg7N7-TU'} + dapUtils.dapRefreshMembership(sampleConfig, sampleCachedToken.token, onDone) + let request = server.requests[0]; + request.respond(200, responseHeader, JSON.stringify(membership)); + let rtdObj = dapUtils.dapGetRtdObj(membership, 503) + expect(config.getConfig().ortb2.user.data).to.deep.include.members(rtdObj.rtd.ortb2.user.data); + }); + + it('test dapRefreshMembership success response with exp claim', function () { + let membership = {'cohorts': ['9', '11', '13'], 'said': 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIiwiZXhwIjoxNjQ3OTcxNTU4fQ..ptdM5WO-62ypXlKxFXD4FQ.waEo9MHS2NYQCi-zh_p6HgT9BdqGyQbBq4GfGLfsay4nRBgICsTS-VkV6e7xx5U1T8BgpKkRJIZBwTOY5Pkxk9FpK5nnffDSEljRrp1LXLCkNP4qwrlqHInFbZsonNWW4_mW-7aUPlTwIsTbfjTuyHdXHeQa1ALrwFFFWE7QUmPNd2RsHjDwUsxlJPEb5TnHn5W0Mgo_PQZaxvhJInMbxPgtJLoqnJvOqCBEoQY7au7ALZL_nWK8XIwPMF19J7Z3cBg9vQInhr_E3rMdQcAFHEzYfgoNcIYCCR0t1UOqUE3HNtX-E64kZAYKWdlsBb9eW5Gj9hHYyPNL_4Hntjg5eLXGpsocMg0An-qQKGC6hkrxKzeM-GrjpvSaQLNs4iqDpHUtzA02LW_vkLkMNRUiyXVJ3FUZwfyq6uHSRKWZ6UFdAfL0rfJ8q8x8Ll-qJO2Jfyvidlsi9FIs7x1WJrvDCKepfAQM1UXRTonrQljFBAk83PcL2bmWuJDgJZ0lWS4VnZbIf6A7fDourmkDxdVRptvQq5nSjtzCA6whRw0-wGz8ehNJsaJw9H_nG9k4lRKs7A5Lqsyy7TVFrAPjnA_Q1a2H6xF2ULxrtIqoNqdX7k9RjowEZSQlZgZUOAmI4wzjckdcSyC_pUlYBMcBwmlld34mmOJe9EBHAxjdci7Q_9lvj1HTcwGDcQITXnkW9Ux5Jkt9Naw-IGGrnEIADaT2guUAto8W_Gb05TmwHSd6DCmh4zepQCbqeVe6AvPILtVkTgsTTo27Q-NvS7h-XtthJy8425j5kqwxxpZFJ0l0ytc6DUyNCLJXuxi0JFU6-LoSXcROEMVrHa_Achufr9vHIELwacSAIHuwseEvg_OOu1c1WYEwZH8ynBLSjqzy8AnDj24hYgA0YanPAvDqacrYrTUFqURbHmvcQqLBTcYa_gs7uDx4a1EjtP_NvHRlvCgGAaASrjGMhTX8oJxlTqahhQ.pXm-7KqnNK8sbyyczwkVYhcjgiwkpO8LjBBVw4lcyZE'} + dapUtils.dapRefreshMembership(sampleConfig, sampleCachedToken.token, onDone) + let request = server.requests[0]; + request.respond(200, responseHeader, JSON.stringify(membership)); + let rtdObj = dapUtils.dapGetRtdObj(membership, 503) + expect(config.getConfig().ortb2.user.data).to.deep.include.members(rtdObj.rtd.ortb2.user.data); + expect(JSON.parse(storage.getDataFromLocalStorage(DAP_MEMBERSHIP)).expires_at).to.be.equal(1647971548); + }); + + it('test dapRefreshMembership 400 error response', function () { + dapUtils.dapRefreshMembership(sampleConfig, sampleCachedToken.token, onDone) + let request = server.requests[0]; + request.respond(400, responseHeader, 'error'); + expect(config.getConfig().ortb2).to.be.equal(undefined); + }); + + it('test dapRefreshMembership 403 error response', function () { + dapUtils.dapRefreshMembership(sampleConfig, sampleCachedToken.token, onDone) + let request = server.requests[0]; + request.respond(403, responseHeader, 'error'); + let requestTokenize = server.requests[1]; + responseHeader['Akamai-DAP-Token'] = sampleCachedToken.token; + requestTokenize.respond(200, responseHeader, ''); + let requestMembership = server.requests[2]; + requestMembership.respond(403, responseHeader, 'error'); + expect(server.requests.length).to.be.equal(DAP_MAX_RETRY_TOKENIZE + 2); + }); + }); + + describe('dapRefreshEncryptedMembership test', function () { + it('test dapRefreshEncryptedMembership success response', function () { + let expiry = Math.round(Date.now() / 1000.0) + 3600; // in seconds + let encMembership = 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoic29tZXNlY3JldGludmF1bHQifQ..f8_At4OqeQXyQcSwThOJ_w.69ImVQ3bEZ6QP7ROCRpAJjNcKY49SEPYR6qTp_8l7L8kQdPbpi4wmuOzt78j7iBrX64k2wltzmQFjDmVKSxDhrEguxpgx6t-L1tT8ZA0UosMWpVsgmKEZxOn2e9ES3jw8RNCS4WSWocSPQX33xSb51evXjm9E1s0tGoLnwXl0GsUvzRsSU86wQG6RZnAQTi7s-r-M2TKibdDjUqgIt62vJ-aBZ7RWw91MINgOdmDNs1bFfbBX5Cy1kd4-kjvRDz_aJ6zHX4sK_7EmQhGEY3tW-A3_l2I88mw-RSJaPkb_IWg0QpVwXDaE2F2g8NpY1PzCRvG_NIE8r28eK5q44OMVitykHmKmBXGDj7z2JVgoXkfo5u0I-dypZARn4GP_7niK932avB-9JD7Mz3TrlU4GZ7IpYfJ91PMsRhrs5xNPQwLZbpuhF76A7Dp7iss71UjkGCiPTU6udfRb4foyf_7xEF66m1eQVcVaMdxEbMuu9GBfdr-d04TbtJhPfUV8JfxTenvRYoi13n0j5kH0M5OgaSQD9kQ3Mrd9u-Cms-BGtT0vf-N8AaFZY_wn0Y4rkpv5HEaH7z3iT4RCHINWrXb_D0WtjLTKQi2YmF8zMlzUOewNJGwZRwbRwxc7JoDIKEc5RZkJYevfJXOEEOPGXZ7AGZxOEsJawPqFqd_nOUosCZS4akHhcDPcVowoecVAV0hhhoS6JEY66PhPp1snbt6yqA-fQhch7z8Y-DZT3Scibvffww3Scg_KFANWp0KeEvHG0vyv9R2F4o66viSS8y21MDnM7Yjk8C-j7aNMldUQbjN_7Yq1nkfe0jiBX_hsINBRPgJHUY4zCaXuyXs-JZZfU92nwG0RT3A_3RP2rpY8-fXp9d3C2QJjEpnmHvTMsuAZCQSBe5DVrJwN_UKedxcJEoOt0wLz6MaCMyYZPd8tnQeqYK1cd3RgQDXtzKC0HDw1En489DqJXEst4eSSkaaW1lImLeaF8XCOaIqPqoyGk4_6KVLw5Q7OnpczuXqYKMd9UTMovGeuTuo1k0ddfEqTq9QwxkwZL51AiDRnwTCAeYBU1krV8FCJQx-mH_WPB5ftZj-o_3pbvANeRk27QBVmjcS-tgDllJkWBxX-4axRXzLw8pUUUZUT_NOL0OiqUCWVm0qMBEpgRQ57Se42-hkLMTzLhhGJOnVcaXU1j4ep-N7faNvbgREBjf_LgzvaWS90a2NJ9bB_J9FyXelhCN_AMLfdOS3fHkeWlZ0u0PMbn5DxXRMe0l9jB-2VJZhcPQRlWoYyoCO3l4F5ZmuQP5Xh9CU4tvSWih6jlwMDgdVWuTpdfPD5bx8ccog3JDq87enx-QtPzLU3gMgouNARJGgNwKS_GJSE1uPrt2oiqgZ3Z0u_I5MKvPdQPV3o-4rsaE730eB4OwAOF-mkGWpzy8Pbl-Qe5PR9mHBhuyJgZ-WDSCHl5yvet2kfO9mPXZlqBQ26fzTcUYH94MULAZn36og6w.3iKGv-Le-AvRmi26W1v6ibRLGbwKbCR92vs-a9t55hw'; + dapUtils.dapRefreshEncryptedMembership(esampleConfig, sampleCachedToken.token, onDone) + let request = server.requests[0]; + responseHeader['Akamai-DAP-Token'] = encMembership; + request.respond(200, responseHeader, encMembership); + let rtdObj = dapUtils.dapGetEncryptedRtdObj({'encryptedSegments': encMembership}, 504) + expect(config.getConfig().ortb2.user.data).to.deep.include.members(rtdObj.rtd.ortb2.user.data); + expect(JSON.parse(storage.getDataFromLocalStorage(DAP_ENCRYPTED_MEMBERSHIP)).expires_at).to.equal(expiry); + }); + + it('test dapRefreshEncryptedMembership success response with exp claim', function () { + let encMembership = 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoic29tZXNlY3JldGludmF1bHQiLCJleHAiOjE2NDM4MzA2NDB9..inYoxwht_aqTIWqGhEm_Gw.wDcCUOCwtqgnNUouaD723gKfm7X7bgkHgtiX4mr07P3tWk25PUQunmwTLhWBB5CYzzGIfIvveG_u4glNRLi_eRSQV4ihKKk1AN-BSSJ3d0CLAdY9I1WG5vX1VmopXyKnV90bl9SLNqnhg4Vxe6YU4ogTYxsKHuIN1EeIH4hpl-HbCQWQ1DQt4mB-MQF8V9AWTfU0D7sFMSK8f9qj6NGmf1__oHdHUlws0t5V2UAn_dhJexsuREK_gh65pczCuly5eEcziZ82LeP-nOhKWSRHB_tS_mKXrRU6_At_EVDgtfA3PSBJ6eQylCii6bTL42vZzz4jZhJv_3eLfRdKqpVT5CWNBzcDoQ2VcQgKgIBtPJ45KFfAYTQ6kdl21QMSjqtu8GTsv1lEZtrqHY6zRiG8_Mu28-PmjEw4LDdZmBDOeroue_MJD6wuE_jlE7J2iVdo8CkVnoRgzFwNbKBo7CK4z0WahV9rhuOm0LKAN5H0jF_gj696U-3fVTDTIb8ndNKNI2_xAhvWs00BFGtUtWgr8QGDGRTDCNGsDgnb_Vva9xCqVOyAE9O3Fq1QYl-tMA-KkBt3zzvmFFpOxpOyH-lUubKLKlsrxKc3GSyVEQ9DDLhrXXJgR5H5BSE4tjlK7p3ODF5qz0FHtIj7oDcgLazFO7z2MuFy2LjJmd3hKl6ujcfYEDiQ4D3pMIo7oiU33aFBD1YpzI4-WzNfJlUt1FoK0-DAXpbbV95s8p08GOD4q81rPw5hRADKJEr0QzrbDwplTWCzT2fKXMg_dIIc5AGqGKnVRUS6UyF1DnHpudNIJWxyWZjWIEw_QNjU0cDFmyPSyKxNrnfq9w8WE2bfbS5KTicxei5QHnC-cnL7Nh7IXp7WOW6R1YHbNPT7Ad4OhnlV-jjrXwkSv4wMAbfwAWoSCchGh7uvENNAeJymuponlJbOgw_GcYM73hMs8Z8W9qxRfbyF4WX5fDKXg61mMlaieHkc0EnoC5q7uKyXuZUehHZ76JLDFmewslLkQq5SkVCttzJePBnY1ouPEHw5ZTzUnG5f01QQOVcjIN-AqXNDbG5IOwq0heyS6vVfq7lZKJdLDVQ21qRjazGPaqYwLzugkWkzCOzPTgyFdbXzgjfmJwylHSOM5Jpnul84GzxEQF-1mHP2A8wtIT-M7_iX24It2wwWvc8qLA6GEqruWCtNyoug8CXo44mKdSSCGeEZHtfMbzXdLIBHCy2jSHz5i8S7DU_R7rE_5Ssrb81CqIYbgsAQBHtOYoyvzduTOruWcci4De0QcULloqImIEHUuIe2lnYO889_LIx5p7nE3UlSvLBo0sPexavFUtHqI6jdG6ye9tdseUEoNBDXW0aWD4D-KXX1JLtAgToPVUtEaXCJI7QavwO9ZG6UZM6jbfuJ5co0fvUXp6qYrFxPQo2dYHkar0nT6s1Zg5l2g8yWlLUJrHdHAzAw_NScUp71OpM4TmNsLnYaPVPcOxMvtJXTanbNWr0VKc8gy9q3k_1XxAnQwiduNs7f5bA-6qCVpayHv5dE7mUhFEwyh1_w95jEaURsQF_hnnd2OqRkADfiok4ZiPU2b38kFW1LXjpI39XXES3JU0e08Rq2uuelyLbCLWuJWq_axuKSZbZvpYeqWtIAde8FjCiO7RPlEc0nyzWBst8RBxQ-Bekg9UXPhxBRcm0HwA.Q2cBSFOQAC-QKDwmjrQXnVQd3jNOppMl9oZfd2yuKeY'; + dapUtils.dapRefreshEncryptedMembership(esampleConfig, sampleCachedToken.token, onDone) + let request = server.requests[0]; + responseHeader['Akamai-DAP-Token'] = encMembership; + request.respond(200, responseHeader, encMembership); + let rtdObj = dapUtils.dapGetEncryptedRtdObj({'encryptedSegments': encMembership}, 504) + expect(config.getConfig().ortb2.user.data).to.deep.include.members(rtdObj.rtd.ortb2.user.data); + expect(JSON.parse(storage.getDataFromLocalStorage(DAP_ENCRYPTED_MEMBERSHIP)).expires_at).to.equal(1643830630); + }); + + it('test dapRefreshEncryptedMembership error response', function () { + dapUtils.dapRefreshEncryptedMembership(esampleConfig, sampleCachedToken.token, onDone) + let request = server.requests[0]; + request.respond(400, responseHeader, 'error'); + expect(config.getConfig().ortb2).to.be.equal(undefined); + }); + + it('test dapRefreshEncryptedMembership 403 error response', function () { + generateRealTimeData({}, () => {}, emoduleConfig, {}); + dapUtils.dapRefreshEncryptedMembership(esampleConfig, sampleCachedToken.token, onDone) + let request = server.requests[0]; + request.respond(403, responseHeader, 'error'); + let requestTokenize = server.requests[1]; + responseHeader['Akamai-DAP-Token'] = sampleCachedToken.token; + requestTokenize.respond(200, responseHeader, ''); + let requestMembership = server.requests[2]; + requestMembership.respond(403, responseHeader, 'error'); + expect(server.requests.length).to.be.equal(DAP_MAX_RETRY_TOKENIZE + 2); + }); + }); + + describe('dapGetEncryptedMembershipFromLocalStorage test', function () { + it('test dapGetEncryptedMembershipFromLocalStorage function with valid cache', function () { + storage.setDataInLocalStorage(DAP_ENCRYPTED_MEMBERSHIP, JSON.stringify(cachedEncryptedMembership)) + expect(JSON.stringify(dapUtils.dapGetEncryptedMembershipFromLocalStorage())).to.equal(JSON.stringify(encMembership)); + }); + + it('test dapGetEncryptedMembershipFromLocalStorage function with invalid cache', function () { + let expiry = Math.round(Date.now() / 1000.0) - 100; // in seconds + let encMembership = {'expiry': expiry, 'encryptedSegments': cachedEncryptedMembership.encryptedSegments} + storage.setDataInLocalStorage(DAP_ENCRYPTED_MEMBERSHIP, JSON.stringify(encMembership)) + expect(dapUtils.dapGetEncryptedMembershipFromLocalStorage()).to.equal(null); + }); + }); + + describe('Akamai-DAP-SS-ID test', function () { + it('Akamai-DAP-SS-ID present in response header', function () { + let expiry = Math.round(Date.now() / 1000.0) + 300; // in seconds + dapUtils.dapRefreshToken(sampleConfig, false, onDone) + let request = server.requests[0]; + let sampleSSID = 'Test_SSID_Spec'; + responseHeader['Akamai-DAP-Token'] = sampleCachedToken.token; + responseHeader['Akamai-DAP-SS-ID'] = sampleSSID; + request.respond(200, responseHeader, ''); + expect(storage.getDataFromLocalStorage(DAP_SS_ID)).to.be.equal(JSON.stringify(sampleSSID)); + }); + + it('Test if Akamai-DAP-SS-ID is present in request header', function () { + let expiry = Math.round(Date.now() / 1000.0) + 100; // in seconds + storage.setDataInLocalStorage(DAP_SS_ID, JSON.stringify('Test_SSID_Spec')) + dapUtils.dapRefreshToken(sampleConfig, false, onDone) + let request = server.requests[0]; + let ssidHeader = request.requestHeaders['Akamai-DAP-SS-ID']; + responseHeader['Akamai-DAP-Token'] = sampleCachedToken.token; + request.respond(200, responseHeader, ''); + expect(ssidHeader).to.be.equal('Test_SSID_Spec'); + }); + }); + + describe('Test gdpr and usp consent handling', function () { + it('Gdpr applies and gdpr consent string not present', function () { + expect(akamaiDapRtdSubmodule.init(null, sampleGdprConsentConfig)).to.equal(false); + }); + + it('Gdpr applies and gdpr consent string is present', function () { + sampleGdprConsentConfig.gdpr.consentString = 'BOJ/P2HOJ/P2HABABMAAAAAZ+A=='; + expect(akamaiDapRtdSubmodule.init(null, sampleGdprConsentConfig)).to.equal(true); + }); + + it('USP consent present and user have opted out', function () { + expect(akamaiDapRtdSubmodule.init(null, sampleUspConsentConfig)).to.equal(false); + }); + + it('USP consent present and user have not been provided with option to opt out', function () { + expect(akamaiDapRtdSubmodule.init(null, {'usp': '1NYY'})).to.equal(false); + }); + + it('USP consent present and user have not opted out', function () { + expect(akamaiDapRtdSubmodule.init(null, {'usp': '1YNY'})).to.equal(true); }); }); }); From f11bcdaa0695662e13faee53520ae176fbb0ee29 Mon Sep 17 00:00:00 2001 From: asurovenko-zeta <80847074+asurovenko-zeta@users.noreply.github.com> Date: Tue, 24 May 2022 20:51:05 +0700 Subject: [PATCH 30/76] Zeta global ssp bid adapter: add shortname param (#8454) * zeta_global_sspBidAdapter shortname was added * remove the trash Co-authored-by: Surovenko Alexey --- modules/zeta_global_sspBidAdapter.js | 2 +- .../modules/zeta_global_sspBidAdapter_spec.js | 61 +++++++++++-------- 2 files changed, 38 insertions(+), 25 deletions(-) diff --git a/modules/zeta_global_sspBidAdapter.js b/modules/zeta_global_sspBidAdapter.js index 87fdbe1396f..1fdd1eff179 100644 --- a/modules/zeta_global_sspBidAdapter.js +++ b/modules/zeta_global_sspBidAdapter.js @@ -99,7 +99,7 @@ export const spec = { user: params.user ? params.user : {}, app: params.app ? params.app : {}, ext: { - tags: params.tags ? params.tags : {}, + tags: {...params.tags, shortname: params.shortname}, sid: params.sid ? params.sid : undefined } }; diff --git a/test/spec/modules/zeta_global_sspBidAdapter_spec.js b/test/spec/modules/zeta_global_sspBidAdapter_spec.js index 20113a63994..f6faa67f6b8 100644 --- a/test/spec/modules/zeta_global_sspBidAdapter_spec.js +++ b/test/spec/modules/zeta_global_sspBidAdapter_spec.js @@ -25,6 +25,25 @@ describe('Zeta Ssp Bid Adapter', function () { } ]; + const params = { + user: { + uid: 222, + buyeruid: 333 + }, + tags: { + someTag: 444, + }, + sid: 'publisherId', + shortname: 'test_shortname', + site: { + page: 'testPage' + }, + app: { + bundle: 'testBundle' + }, + test: 1 + }; + const bannerRequest = [{ bidId: 12345, auctionId: 67890, @@ -41,18 +60,7 @@ describe('Zeta Ssp Bid Adapter', function () { consentString: 'consentString' }, uspConsent: 'someCCPAString', - params: { - placement: 111, - user: { - uid: 222, - buyeruid: 333 - }, - tags: { - someTag: 444, - sid: 'publisherId' - }, - test: 1 - }, + params: params, userIdAsEids: eids }]; @@ -72,18 +80,7 @@ describe('Zeta Ssp Bid Adapter', function () { refererInfo: { referer: 'http://www.zetaglobal.com/page?param=video' }, - params: { - placement: 111, - user: { - uid: 222, - buyeruid: 333 - }, - tags: { - someTag: 444, - sid: 'publisherId' - }, - test: 1 - }, + params: params }]; it('Test the bid validation function', function () { @@ -269,4 +266,20 @@ describe('Zeta Ssp Bid Adapter', function () { expect(payload.imp[0].banner).to.be.undefined; }); + + it('Test required params in banner request', function () { + const request = spec.buildRequests(bannerRequest, bannerRequest[0]); + const payload = JSON.parse(request.data); + expect(payload.ext.sid).to.eql('publisherId'); + expect(payload.ext.tags.someTag).to.eql(444); + expect(payload.ext.tags.shortname).to.eql('test_shortname'); + }); + + it('Test required params in video request', function () { + const request = spec.buildRequests(videoRequest, videoRequest[0]); + const payload = JSON.parse(request.data); + expect(payload.ext.sid).to.eql('publisherId'); + expect(payload.ext.tags.someTag).to.eql(444); + expect(payload.ext.tags.shortname).to.eql('test_shortname'); + }); }); From a6e3c449e0e7609386dc81ef5eb68ff1112ba882 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Tue, 24 May 2022 11:21:26 -0700 Subject: [PATCH 31/76] Remove medianetRtdProvider tests (#8463) --- test/spec/modules/medianetRtdProvider_spec.js | 146 ------------------ 1 file changed, 146 deletions(-) delete mode 100644 test/spec/modules/medianetRtdProvider_spec.js diff --git a/test/spec/modules/medianetRtdProvider_spec.js b/test/spec/modules/medianetRtdProvider_spec.js deleted file mode 100644 index 7d73ecd5d44..00000000000 --- a/test/spec/modules/medianetRtdProvider_spec.js +++ /dev/null @@ -1,146 +0,0 @@ -import * as medianetRTD from '../../../modules/medianetRtdProvider.js'; -import * as sinon from 'sinon'; -import { assert } from 'chai'; - -let sandbox; -let setDataSpy; -let getTargetingDataSpy; -let onPrebidRequestBidSpy; - -const conf = { - dataProviders: [{ - 'name': 'medianet', - 'params': { - 'cid': 'customer_id', - } - }] -}; - -describe('medianet realtime module', function () { - beforeEach(function () { - sandbox = sinon.sandbox.create(); - window.mnjs = window.mnjs || {}; - window.mnjs.que = window.mnjs.que || []; - window.mnjs.setData = setDataSpy = sandbox.spy(); - window.mnjs.getTargetingData = getTargetingDataSpy = sandbox.spy(); - window.mnjs.onPrebidRequestBid = onPrebidRequestBidSpy = sandbox.spy(); - }); - - afterEach(function () { - sandbox.restore(); - window.mnjs = {}; - }); - - it('init should return false when customer id is passed', function () { - assert.equal(medianetRTD.medianetRtdModule.init({}), false); - }); - - it('init should return true when customer id is passed', function () { - assert.equal(medianetRTD.medianetRtdModule.init(conf.dataProviders[0]), true); - }); - - it('init should pass config to js when loaded', function () { - medianetRTD.medianetRtdModule.init(conf.dataProviders[0]); - - const command = window.mnjs.que.pop(); - assert.isFunction(command); - command(); - - assert.equal(setDataSpy.called, true); - assert.equal(setDataSpy.args[0][0].name, 'initIRefresh'); - }); - - it('auctionInit should pass information to js when loaded', function () { - const auctionObject = {adUnits: []}; - medianetRTD.medianetRtdModule.onAuctionInitEvent(auctionObject); - - const command = window.mnjs.que.pop(); - assert.isFunction(command); - command(); - - assert.equal(setDataSpy.called, true); - assert.equal(setDataSpy.args[0][0].name, 'auctionInit'); - assert.deepEqual(setDataSpy.args[0][0].data, {auction: auctionObject}); - }); - - describe('getTargeting should work correctly', function () { - it('should return empty if not loaded', function () { - window.mnjs.loaded = false; - assert.deepEqual(medianetRTD.medianetRtdModule.getTargetingData([]), {}); - }); - - it('should return ad unit codes when ad units are present', function () { - const adUnitCodes = ['code1', 'code2']; - assert.deepEqual(medianetRTD.medianetRtdModule.getTargetingData(adUnitCodes), { - code1: {'mnadc': 'code1'}, - code2: {'mnadc': 'code2'}, - }); - }); - - it('should call mnjs.getTargetingData if loaded', function () { - window.mnjs.loaded = true; - medianetRTD.medianetRtdModule.getTargetingData([]); - assert.equal(getTargetingDataSpy.called, true); - }); - }); - - describe('getBidRequestData should work correctly', function () { - it('callback should be called when we are not interested in request', function () { - const requestBidsProps = { - adUnits: [{ - code: 'code1', bids: [], - }], - adUnitCodes: ['code1'], - }; - const callbackSpy = sandbox.spy(); - medianetRTD.medianetRtdModule.getBidRequestData(requestBidsProps, callbackSpy, conf.dataProviders[0], {}); - - const command = window.mnjs.que.pop(); - assert.isFunction(command); - command(); - - assert.equal(onPrebidRequestBidSpy.called, true, 'onPrebidRequest should always be called'); - assert.equal(callbackSpy.called, true, 'when onPrebidRequest returns nothing callback should be called immediately'); - }); - - it('we should wait for callback till onComplete', function () { - const requestBidsProps = { - adUnits: [{ - code: 'code1', bids: [], - }], - adUnitCodes: ['code1'], - }; - - const refreshInformation = { - mnrf: '1', - mnrfc: 2, - }; - - const callbackSpy = sandbox.spy(); - const onCompleteSpy = sandbox.spy(); - window.mnjs.onPrebidRequestBid = onPrebidRequestBidSpy = () => { - onPrebidRequestBidSpy.called = true; - return {onComplete: onCompleteSpy}; - }; - medianetRTD.medianetRtdModule.getBidRequestData(requestBidsProps, callbackSpy, conf.dataProviders[0], {}); - - const command = window.mnjs.que.pop(); - assert.isFunction(command); - command(); - - assert.equal(callbackSpy.called, false, 'callback should not be called, as we are returning a request from onPrebidRequestBid'); - assert.equal(onPrebidRequestBidSpy.called, true, 'onPrebidRequestBid should be called once'); - assert.equal(onCompleteSpy.called, true, 'onComplete should be passed callback'); - assert.isFunction(onCompleteSpy.args[0][0], 'onCompleteSpy first argument error callback should be a function'); - assert.isFunction(onCompleteSpy.args[0][1], 'onCompleteSpy second argument success callback should be a function'); - onCompleteSpy.args[0][0](); - assert.equal(callbackSpy.callCount, 1, 'callback should be called when error callback is triggered'); - onCompleteSpy.args[0][1]({}, { - 'code1': {ext: {refresh: refreshInformation}} - }); - assert.equal(callbackSpy.callCount, 2, 'callback should be called when success callback is triggered'); - assert.isObject(requestBidsProps.adUnits[0].ortb2Imp, 'ORTB object should be set'); - assert.deepEqual(requestBidsProps.adUnits[0].ortb2Imp.ext.refresh, refreshInformation, 'ORTB should have refresh information should be set'); - }); - }); -}); From 170f6fd547047e69fba3416ea1351d79ef382b81 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Tue, 24 May 2022 14:23:40 -0400 Subject: [PATCH 32/76] Revert "Akamai RTD: fixed bugs on rtd module and added the entropy values (#8284)" (#8464) This reverts commit c4348892b5d3425d21373e1dcf4d67da04fed622. --- .../gpt/akamaidap_segments_example.html | 8 +- modules/.submodules.json | 1 - modules/akamaiDapRtdProvider.js | 586 ++++-------------- modules/akamaiDapRtdProvider.md | 8 +- src/adloader.js | 3 +- .../spec/modules/akamaiDapRtdProvider_spec.js | 510 ++++----------- 6 files changed, 243 insertions(+), 873 deletions(-) diff --git a/integrationExamples/gpt/akamaidap_segments_example.html b/integrationExamples/gpt/akamaidap_segments_example.html index b4e7495002e..e85ac8e1337 100644 --- a/integrationExamples/gpt/akamaidap_segments_example.html +++ b/integrationExamples/gpt/akamaidap_segments_example.html @@ -68,7 +68,6 @@ } }, realTimeData: { - auctionDelay: 2000, dataProviders: [ { name: "dap", @@ -77,10 +76,9 @@ apiHostname: "prebid.dap.akadns.net", apiVersion: "x1", domain: "prebid.org", - identityType: "dap-signature:1.3.0", - segtax: 504, - dapFpUrl: 'https://dap-dist.akamaized.net/dapfingerprinting.js', - dapFpTimeout: 1500 + identityType: "dap-signature:1.0.0", + segtax: 503, + tokenTtl: 5, } } ] diff --git a/modules/.submodules.json b/modules/.submodules.json index 8d62f30d7c4..9402d0561fa 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -52,7 +52,6 @@ ], "rtdModule": [ "airgridRtdProvider", - "akamaiDapRtdProvider", "browsiRtdProvider", "dgkeywordRtdProvider", "geoedgeRtdProvider", diff --git a/modules/akamaiDapRtdProvider.js b/modules/akamaiDapRtdProvider.js index 0b042da8c6e..aca984d39c8 100644 --- a/modules/akamaiDapRtdProvider.js +++ b/modules/akamaiDapRtdProvider.js @@ -10,22 +10,12 @@ import {config} from '../src/config.js'; import {getStorageManager} from '../src/storageManager.js'; import {submodule} from '../src/hook.js'; import {isPlainObject, mergeDeep, logMessage, logInfo, logError} from '../src/utils.js'; -import { loadExternalScript } from '../src/adloader.js'; const MODULE_NAME = 'realTimeData'; const SUBMODULE_NAME = 'dap'; -const MODULE_CODE = 'akamaidap'; - -export const DAP_TOKEN = 'async_dap_token'; -export const DAP_MEMBERSHIP = 'async_dap_membership'; -export const DAP_ENCRYPTED_MEMBERSHIP = 'encrypted_dap_membership'; -export const DAP_SS_ID = 'dap_ss_id'; -export const DAP_DEFAULT_TOKEN_TTL = 3600; // in seconds -export const DAP_MAX_RETRY_TOKENIZE = 1; -export const DAP_CLIENT_ENTROPY = 'dap_client_entropy' +export const SEGMENTS_STORAGE_KEY = 'akamaiDapSegments'; export const storage = getStorageManager({gvlid: null, moduleName: SUBMODULE_NAME}); -let dapRetryTokenize = 0; /** * Lazy merge objects. @@ -63,92 +53,60 @@ export function addRealTimeData(rtd) { * Real-time data retrieval from Audigent * @param {Object} reqBidsConfigObj * @param {function} onDone - * @param {Object} rtdConfig + * @param {Object} rtdConfi * @param {Object} userConsent */ export function getRealTimeData(bidConfig, onDone, rtdConfig, userConsent) { - let entropyDict = JSON.parse(storage.getDataFromLocalStorage(DAP_CLIENT_ENTROPY)); - let loadScriptPromise = new Promise((resolve, reject) => { - if (rtdConfig && rtdConfig.params && rtdConfig.params.dapFpTimeout && Number.isInteger(rtdConfig.params.dapFpTimeout)) { - setTimeout(reject, rtdConfig.params.dapFpTimeout, Error('DapFP script could not be loaded')); - } - if (entropyDict && entropyDict.expires_at > Math.round(Date.now() / 1000.0)) { - logMessage('Using cached entropy'); - resolve(); - } else { - if (typeof window.dapCalculateEntropy === 'function') { - window.dapCalculateEntropy(resolve, reject); - } else { - if (rtdConfig && rtdConfig.params && dapUtils.isValidHttpsUrl(rtdConfig.params.dapFpUrl)) { - loadExternalScript(rtdConfig.params.dapFpUrl, MODULE_CODE, () => { dapUtils.dapGetEntropy(resolve, reject) }); - } else { - reject(Error('Please check if dapFpUrl is specified and is valid under config.params')); - } - } - } - }); - loadScriptPromise - .catch((error) => { - logError('Entropy could not be calculated due to: ', error.message); - }) - .finally(() => { - generateRealTimeData(bidConfig, onDone, rtdConfig, userConsent); - }); -} - -export function generateRealTimeData(bidConfig, onDone, rtdConfig, userConsent) { logInfo('DEBUG(getRealTimeData) - ENTER'); logMessage(' - apiHostname: ' + rtdConfig.params.apiHostname); logMessage(' - apiVersion: ' + rtdConfig.params.apiVersion); - dapRetryTokenize = 0; - var jsonData = null; - if (rtdConfig && isPlainObject(rtdConfig.params)) { - if (rtdConfig.params.segtax == 504) { - let encMembership = dapUtils.dapGetEncryptedMembershipFromLocalStorage(); - if (encMembership) { - jsonData = dapUtils.dapGetEncryptedRtdObj(encMembership, rtdConfig.params.segtax) - } - } else { - let membership = dapUtils.dapGetMembershipFromLocalStorage(); - if (membership) { - jsonData = dapUtils.dapGetRtdObj(membership, rtdConfig.params.segtax) - } - } - } + let jsonData = storage.getDataFromLocalStorage(SEGMENTS_STORAGE_KEY); if (jsonData) { - if (jsonData.rtd) { - addRealTimeData(jsonData.rtd); + let data = JSON.parse(jsonData); + if (data.rtd) { + addRealTimeData(data.rtd); onDone(); logInfo('DEBUG(getRealTimeData) - 1'); // Don't return - ensure the data is always fresh. } } - // Calling setTimeout to release the main thread so that the bid request could be sent. - setTimeout(callDapAPIs, 0, bidConfig, onDone, rtdConfig, userConsent); -} -function callDapAPIs(bidConfig, onDone, rtdConfig, userConsent) { if (rtdConfig && isPlainObject(rtdConfig.params)) { let config = { api_hostname: rtdConfig.params.apiHostname, api_version: rtdConfig.params.apiVersion, domain: rtdConfig.params.domain, - segtax: rtdConfig.params.segtax, - identity: {type: rtdConfig.params.identityType} + segtax: rtdConfig.params.segtax }; - let refreshMembership = true; - let token = dapUtils.dapGetTokenFromLocalStorage(); - logMessage('token is: ', token); - if (token !== null) { // If token is not null then check the membership in storage and add the RTD object - if (config.segtax == 504) { // Follow the encrypted membership path - dapUtils.dapRefreshEncryptedMembership(config, token, onDone) // Get the encrypted membership from server - refreshMembership = false; - } else { - dapUtils.dapRefreshMembership(config, token, onDone) // Get the membership from server - refreshMembership = false; - } + let identity = { + type: rtdConfig.params.identityType + }; + let token = dapUtils.dapGetToken(config, identity, rtdConfig.params.tokenTtl); + if (token !== null) { + let membership = dapUtils.dapGetMembership(config, token); + let udSegment = dapUtils.dapMembershipToRtbSegment(membership, config); + logMessage('DEBUG(getRealTimeData) - token: ' + token + ', user.data.segment: ', udSegment); + let data = { + rtd: { + ortb2: { + user: { + data: [ + udSegment + ] + }, + site: { + ext: { + data: { + dapSAID: membership.said + } + } + } + } + } + }; + storage.setDataInLocalStorage(SEGMENTS_STORAGE_KEY, JSON.stringify(data)); + onDone(); } - dapUtils.dapRefreshToken(config, refreshMembership, onDone) // Refresh Token and membership in all the cases } } @@ -159,9 +117,6 @@ function callDapAPIs(bidConfig, onDone, rtdConfig, userConsent) { * @return {boolean} */ function init(provider, userConsent) { - if (dapUtils.checkConsent(userConsent) === false) { - return false; - } return true; } @@ -173,190 +128,104 @@ export const akamaiDapRtdSubmodule = { }; submodule(MODULE_NAME, akamaiDapRtdSubmodule); + export const dapUtils = { - dapGetEntropy: function(resolve, reject) { - if (typeof window.dapCalculateEntropy === 'function') { - window.dapCalculateEntropy(resolve, reject); - } else { - reject(Error('window.dapCalculateEntropy function is not defined')) - } - }, - dapGetTokenFromLocalStorage: function(ttl) { + dapGetToken: function(config, identity, ttl) { let now = Math.round(Date.now() / 1000.0); // in seconds + let storageName = 'async_dap_token'; let token = null; - let item = JSON.parse(storage.getDataFromLocalStorage(DAP_TOKEN)); - if (item) { - if (now < item.expires_at) { - token = item.token; - } + + if (ttl == 0) { + localStorage.removeItem(storageName); } - return token; - }, - dapRefreshToken: function(config, refreshMembership, onDone) { - dapUtils.dapLog('Token missing or expired, fetching a new one...'); - // Trigger a refresh - let now = Math.round(Date.now() / 1000.0); // in seconds - let item = {} - let configAsync = {...config}; - dapUtils.dapTokenize(configAsync, config.identity, onDone, - function(token, status, xhr, onDone) { - item.expires_at = now + DAP_DEFAULT_TOKEN_TTL; - let exp = dapUtils.dapExtractExpiryFromToken(token) - if (typeof exp == 'number') { - item.expires_at = exp - 10; - } - item.token = token; - storage.setDataInLocalStorage(DAP_TOKEN, JSON.stringify(item)); - dapUtils.dapLog('Successfully updated and stored token; expires at ' + item.expires_at); - let dapSSID = xhr.getResponseHeader('Akamai-DAP-SS-ID'); - if (dapSSID) { - storage.setDataInLocalStorage(DAP_SS_ID, JSON.stringify(dapSSID)); - } - let deviceId100 = xhr.getResponseHeader('Akamai-DAP-100'); - if (deviceId100 != null) { - storage.setDataInLocalStorage('dap_deviceId100', deviceId100); - dapUtils.dapLog('Successfully stored DAP 100 Device ID: ' + deviceId100); - } - if (refreshMembership) { - if (config.segtax == 504) { - dapUtils.dapRefreshEncryptedMembership(config, token, onDone); - } else { - dapUtils.dapRefreshMembership(config, token, onDone); + let item = JSON.parse(localStorage.getItem(storageName)); + if (item == null) { + item = { + expires_at: now - 1, + token: null + }; + } else { + token = item.token; + } + + if (now > item.expires_at) { + dapUtils.dapLog('Token missing or expired, fetching a new one...'); + // Trigger a refresh + let configAsync = {...config}; + dapUtils.dapTokenize(configAsync, identity, + function(token, status, xhr) { + item.expires_at = now + ttl; + item.token = token; + localStorage.setItem(storageName, JSON.stringify(item)); + dapUtils.dapLog('Successfully updated and stored token; expires in ' + ttl + ' seconds'); + let deviceId100 = xhr.getResponseHeader('Akamai-DAP-100'); + if (deviceId100 != null) { + localStorage.setItem('dap_deviceId100', deviceId100); + dapUtils.dapLog('Successfully stored DAP 100 Device ID: ' + deviceId100); } + }, + function(xhr, status, error) { + logError('ERROR(' + error + '): failed to retrieve token! ' + status); } - }, - function(xhr, status, error, onDone) { - logError('ERROR(' + error + '): failed to retrieve token! ' + status); - onDone() - } - ); + ); + } + + return token; }, - dapGetMembershipFromLocalStorage: function() { + dapGetMembership: function(config, token) { let now = Math.round(Date.now() / 1000.0); // in seconds + let storageName = 'async_dap_membership'; + let maxTtl = 3600; // if the cached membership is older than this, return null let membership = null; - let item = JSON.parse(storage.getDataFromLocalStorage(DAP_MEMBERSHIP)); - if (item) { - if (now < item.expires_at) { - membership = { - said: item.said, - cohorts: item.cohorts, - attributes: null - }; - } + let item = JSON.parse(localStorage.getItem(storageName)); + if (item == null || (now - item.expires_at) > maxTtl) { + item = { + expires_at: now - 1, + said: null, + cohorts: null, + attributes: null + }; + } else { + membership = { + said: item.said, + cohorts: item.cohorts, + attributes: null + }; } - return membership; - }, - dapRefreshMembership: function(config, token, onDone) { - let now = Math.round(Date.now() / 1000.0); // in seconds - let item = {} + // Always refresh the cached membership. let configAsync = {...config}; - dapUtils.dapMembership(configAsync, token, onDone, - function(membership, status, xhr, onDone) { - item.expires_at = now + DAP_DEFAULT_TOKEN_TTL; - let exp = dapUtils.dapExtractExpiryFromToken(membership.said) - if (typeof exp == 'number') { - item.expires_at = exp - 10; - } + dapUtils.dapMembership(configAsync, token, + function(membership, status, xhr) { + item.expires_at = now + maxTtl; item.said = membership.said; item.cohorts = membership.cohorts; - storage.setDataInLocalStorage(DAP_MEMBERSHIP, JSON.stringify(item)); + localStorage.setItem(storageName, JSON.stringify(item)); dapUtils.dapLog('Successfully updated and stored membership:'); dapUtils.dapLog(item); - - let data = dapUtils.dapGetRtdObj(item, config.segtax) - dapUtils.checkAndAddRealtimeData(data, config.segtax); - onDone(); }, - function(xhr, status, error, onDone) { + function(xhr, status, error) { logError('ERROR(' + error + '): failed to retrieve membership! ' + status); - if (status == 403 && dapRetryTokenize < DAP_MAX_RETRY_TOKENIZE) { - dapRetryTokenize++; - dapUtils.dapRefreshToken(config, true, onDone); - } else { - onDone(); - } } ); - }, - dapGetEncryptedMembershipFromLocalStorage: function() { - let now = Math.round(Date.now() / 1000.0); // in seconds - let encMembership = null; - let item = JSON.parse(storage.getDataFromLocalStorage(DAP_ENCRYPTED_MEMBERSHIP)); - if (item) { - if (now < item.expires_at) { - encMembership = { - encryptedSegments: item.encryptedSegments - }; - } - } - return encMembership; - }, - - dapRefreshEncryptedMembership: function(config, token, onDone) { - let now = Math.round(Date.now() / 1000.0); // in seconds - let item = {}; - let configAsync = {...config}; - dapUtils.dapEncryptedMembership(configAsync, token, onDone, - function(encToken, status, xhr, onDone) { - item.expires_at = now + DAP_DEFAULT_TOKEN_TTL; - let exp = dapUtils.dapExtractExpiryFromToken(encToken) - if (typeof exp == 'number') { - item.expires_at = exp - 10; - } - item.encryptedSegments = encToken; - storage.setDataInLocalStorage(DAP_ENCRYPTED_MEMBERSHIP, JSON.stringify(item)); - dapUtils.dapLog('Successfully updated and stored encrypted membership:'); - dapUtils.dapLog(item); - - let encData = dapUtils.dapGetEncryptedRtdObj(item, config.segtax); - dapUtils.checkAndAddRealtimeData(encData, config.segtax); - onDone(); - }, - function(xhr, status, error, onDone) { - logError('ERROR(' + error + '): failed to retrieve encrypted membership! ' + status); - if (status == 403 && dapRetryTokenize < DAP_MAX_RETRY_TOKENIZE) { - dapRetryTokenize++; - dapUtils.dapRefreshToken(config, true, onDone); - } else { - onDone(); - } - } - ); - }, - - /** - * DESCRIPTION - * Extract expiry value from a token - */ - dapExtractExpiryFromToken: function(token) { - let exp = null; - if (token) { - const tokenArray = token.split('..'); - if (tokenArray && tokenArray.length > 0) { - let decode = atob(tokenArray[0]) - let header = JSON.parse(decode.replace(/"/g, '"')); - exp = header.exp; - } - } - return exp + return membership; }, /** * DESCRIPTION * * Convert a DAP membership response to an OpenRTB2 segment object suitable - * for insertion into user.data.segment or site.data.segment and add it to the rtd obj. + * for insertion into user.data.segment or site.data.segment. */ - dapGetRtdObj: function(membership, segtax) { + dapMembershipToRtbSegment: function(membership, config) { let segment = { name: 'dap.akamai.com', ext: { - 'segtax': segtax + 'segtax': config.segtax }, segment: [] }; @@ -365,83 +234,7 @@ export const dapUtils = { segment.segment.push({ id: i }); } } - let data = { - rtd: { - ortb2: { - user: { - data: [ - segment - ] - }, - site: { - ext: { - data: { - dapSAID: membership.said - } - } - } - } - } - }; - return data; - }, - - /** - * DESCRIPTION - * - * Convert a DAP membership response to an OpenRTB2 segment object suitable - * for insertion into user.data.segment or site.data.segment and add it to the rtd obj. - */ - dapGetEncryptedRtdObj: function(encToken, segtax) { - let segment = { - name: 'dap.akamai.com', - ext: { - 'segtax': segtax - }, - segment: [] - }; - if (encToken != null) { - segment.segment.push({ id: encToken.encryptedSegments }); - } - let encData = { - rtd: { - ortb2: { - user: { - data: [ - segment - ] - } - } - } - }; - return encData; - }, - - checkAndAddRealtimeData: function(data, segtax) { - if (data.rtd) { - if (segtax == 504 && dapUtils.checkIfSegmentsAlreadyExist(data.rtd, 504)) { - logMessage('DEBUG(handleInit): rtb Object already added'); - } else { - addRealTimeData(data.rtd); - } - logInfo('DEBUG(getRealTimeData) - 1'); - } - }, - - checkIfSegmentsAlreadyExist: function(rtd, segtax) { - let segmentsExist = false - let ortb2 = config.getConfig('ortb2') || {}; - if (ortb2.user && ortb2.user.data && ortb2.user.data.length > 0) { - for (let i = 0; i < ortb2.user.data.length; i++) { - let element = ortb2.user.data[i] - if (element.ext && element.ext.segtax == segtax) { - segmentsExist = true - logMessage('DEBUG(checkIfSegmentsAlreadyExist): rtb Object already added: ', ortb2.user.data); - break; - } - } - } - return segmentsExist + return segment; }, dapLog: function(args) { @@ -455,35 +248,6 @@ export const dapUtils = { logInfo('%cDAP Client', css, args); }, - isValidHttpsUrl: function(urlString) { - let url; - try { - url = new URL(urlString); - } catch (_) { - return false; - } - return url.protocol === 'https:'; - }, - - checkConsent: function(userConsent) { - let consent = true; - - if (userConsent && userConsent.gdpr && userConsent.gdpr.gdprApplies) { - const gdpr = userConsent.gdpr; - const hasGdpr = (gdpr && typeof gdpr.gdprApplies === 'boolean' && gdpr.gdprApplies) ? 1 : 0; - const gdprConsentString = hasGdpr ? gdpr.consentString : ''; - if (hasGdpr && (!gdprConsentString || gdprConsentString === '')) { - logError('akamaiDapRtd submodule requires consent string to call API'); - consent = false; - } - } else if (userConsent && userConsent.usp) { - const usp = userConsent.usp; - consent = usp[1] !== 'N' && usp[2] !== 'Y'; - } - - return consent; - }, - /******************************************************************************* * * V2 (And Beyond) API @@ -529,23 +293,23 @@ export const dapUtils = { * function( response, status, xhr } { token = response; }, * function( xhr, status, error ) { ; } // handle error */ - dapTokenize: function(config, identity, onDone, onSuccess = null, onError = null) { + dapTokenize: function(config, identity, onSuccess = null, onError = null) { if (onError == null) { - onError = function(xhr, status, error, onDone) {}; + onError = function(xhr, status, error) {}; } if (config == null || typeof (config) == typeof (undefined)) { - onError(null, 'Invalid config object', 'ClientError', onDone); + onError(null, 'Invalid config object', 'ClientError'); return; } if (typeof (config.domain) != 'string') { - onError(null, 'Invalid config.domain: must be a string', 'ClientError', onDone); + onError(null, 'Invalid config.domain: must be a string', 'ClientError'); return; } if (config.domain.length <= 0) { - onError(null, 'Invalid config.domain: must have non-zero length', 'ClientError', onDone); + onError(null, 'Invalid config.domain: must have non-zero length', 'ClientError'); return; } @@ -554,22 +318,22 @@ export const dapUtils = { } if (typeof (config.api_version) != 'string') { - onError(null, "Invalid api_version: must be a string like 'x1', etc.", 'ClientError', onDone); + onError(null, "Invalid api_version: must be a string like 'x1', etc.", 'ClientError'); return; } if (!(('api_hostname') in config) || typeof (config.api_hostname) != 'string' || config.api_hostname.length == 0) { - onError(null, 'Invalid api_hostname: must be a non-empty string', 'ClientError', onDone); + onError(null, 'Invalid api_hostname: must be a non-empty string', 'ClientError'); return; } if (identity == null || typeof (identity) == typeof (undefined)) { - onError(null, 'Invalid identity object', 'ClientError', onDone); + onError(null, 'Invalid identity object', 'ClientError'); return; } if (!('type' in identity) || typeof (identity.type) != 'string' || identity.type.length <= 0) { - onError(null, "Identity must contain a valid 'type' field", 'ClientError', onDone); + onError(null, "Identity must contain a valid 'type' field", 'ClientError'); return; } @@ -584,11 +348,6 @@ export const dapUtils = { apiParams.attributes = identity.attributes; } - let entropyDict = JSON.parse(storage.getDataFromLocalStorage(DAP_CLIENT_ENTROPY)); - if (entropyDict && entropyDict.entropy) { - apiParams.entropy = entropyDict.entropy; - } - let method; let body; let path; @@ -600,16 +359,10 @@ export const dapUtils = { body = JSON.stringify(apiParams); break; default: - onError(null, 'Invalid api_version: ' + config.api_version, 'ClientError', onDone); + onError(null, 'Invalid api_version: ' + config.api_version, 'ClientError'); return; } - let customHeaders = {'Content-Type': 'application/json'}; - let dapSSID = JSON.parse(storage.getDataFromLocalStorage(DAP_SS_ID)); - if (dapSSID) { - customHeaders['Akamai-DAP-SS-ID'] = dapSSID; - } - let url = 'https://' + config.api_hostname + path; let cb = { success: (response, request) => { @@ -620,16 +373,19 @@ export const dapUtils = { token = request.getResponseHeader('Akamai-DAP-Token'); break; } - onSuccess(token, request.status, request, onDone); + onSuccess(token, request.status, request); }, error: (request, error) => { - onError(request, request.statusText, error, onDone); + onError(request, request.statusText, error); } }; ajax(url, cb, body, { method: method, - customHeaders: customHeaders + customHeaders: { + 'Content-Type': 'application/json', + 'Pragma': 'akamai-x-cache-on' + } }); }, @@ -655,7 +411,7 @@ export const dapUtils = { * api_hostname: 'api.dap.akadns.net', * }; * - * // token from dap_tokenize + * // token from dap_x1_tokenize * * dapMembership( config, token, * function( membership, status, xhr ) { @@ -666,13 +422,13 @@ export const dapUtils = { * } ); * */ - dapMembership: function(config, token, onDone, onSuccess = null, onError = null) { + dapMembership: function(config, token, onSuccess = null, onError = null) { if (onError == null) { - onError = function(xhr, status, error, onDone) {}; + onError = function(xhr, status, error) {}; } if (config == null || typeof (config) == typeof (undefined)) { - onError(null, 'Invalid config object', 'ClientError', onDone); + onError(null, 'Invalid config object', 'ClientError'); return; } @@ -681,32 +437,32 @@ export const dapUtils = { } if (typeof (config.api_version) != 'string') { - onError(null, "Invalid api_version: must be a string like 'x1', etc.", 'ClientError', onDone); + onError(null, "Invalid api_version: must be a string like 'x1', etc.", 'ClientError'); return; } if (!(('api_hostname') in config) || typeof (config.api_hostname) != 'string' || config.api_hostname.length == 0) { - onError(null, 'Invalid api_hostname: must be a non-empty string', 'ClientError', onDone); + onError(null, 'Invalid api_hostname: must be a non-empty string', 'ClientError'); return; } if (token == null || typeof (token) != 'string') { - onError(null, 'Invalid token: must be a non-null string', 'ClientError', onDone); + onError(null, 'Invalid token: must be a non-null string', 'ClientError'); return; } let path = '/data-activation/' + - config.api_version + - '/token/' + token + - '/membership'; + config.api_version + + '/token/' + token + + '/membership'; let url = 'https://' + config.api_hostname + path; let cb = { success: (response, request) => { - onSuccess(JSON.parse(response), request.status, request, onDone); + onSuccess(JSON.parse(response), request.status, request); }, error: (error, request) => { - onError(request, request.status, error, onDone); + onError(request, request.status, error); } }; @@ -714,91 +470,5 @@ export const dapUtils = { method: 'GET', customHeaders: {} }); - }, - - /** - * SYNOPSIS - * - * dapEncryptedMembership( config, token, onSuccess, onError ); - * - * DESCRIPTION - * - * Return the audience segment membership along with a new Secure Advertising - * ID for this token in encrypted format. - * - * PARAMETERS - * - * config: an array of system configuration parameters - * - * token: the token previously returned from the tokenize API - * - * EXAMPLE - * - * config = { - * api_hostname: 'api.dap.akadns.net', - * }; - * - * // token from dap_tokenize - * - * dapEncryptedMembership( config, token, - * function( membership, status, xhr ) { - * // Run auction with membership.segments and membership.said after decryption - * }, - * function( xhr, status, error ) { - * // error - * } ); - * - */ - dapEncryptedMembership: function(config, token, onDone, onSuccess = null, onError = null) { - if (onError == null) { - onError = function(xhr, status, error, onDone) {}; - } - - if (config == null || typeof (config) == typeof (undefined)) { - onError(null, 'Invalid config object', 'ClientError', onDone); - return; - } - - if (!('api_version' in config) || (typeof (config.api_version) == 'string' && config.api_version.length == 0)) { - config.api_version = 'x1'; - } - - if (typeof (config.api_version) != 'string') { - onError(null, "Invalid api_version: must be a string like 'x1', etc.", 'ClientError', onDone); - return; - } - - if (!(('api_hostname') in config) || typeof (config.api_hostname) != 'string' || config.api_hostname.length == 0) { - onError(null, 'Invalid api_hostname: must be a non-empty string', 'ClientError', onDone); - return; - } - - if (token == null || typeof (token) != 'string') { - onError(null, 'Invalid token: must be a non-null string', 'ClientError', onDone); - return; - } - let path = '/data-activation/' + - config.api_version + - '/token/' + token + - '/membership/encrypt'; - - let url = 'https://' + config.api_hostname + path; - - let cb = { - success: (response, request) => { - let encToken = request.getResponseHeader('Akamai-DAP-Token'); - onSuccess(encToken, request.status, request, onDone); - }, - error: (error, request) => { - onError(request, request.status, error, onDone); - } - }; - ajax(url, cb, undefined, { - method: 'GET', - customHeaders: { - 'Content-Type': 'application/json', - 'Pragma': 'akamai-x-get-extracted-values' - } - }); } } diff --git a/modules/akamaiDapRtdProvider.md b/modules/akamaiDapRtdProvider.md index 5e3b93cc5fc..ade11b88602 100644 --- a/modules/akamaiDapRtdProvider.md +++ b/modules/akamaiDapRtdProvider.md @@ -17,7 +17,6 @@ ``` pbjs.setConfig({ realTimeData: { - auctionDelay: 2000, dataProviders: [ { name: "dap", @@ -26,10 +25,9 @@ apiHostname: '', apiVersion: "x1", domain: 'your-domain.com', - identityType: 'email' | 'mobile' | ... | 'dap-signature:1.3.0', - segtax: 504, - dapFpUrl: 'https://dap-dist.akamaized.net/dapfingerprinting.js', - dapFpTimeout: 1500 // Maximum time for dapFP to run + identityType: 'email' | 'mobile' | ... | 'dap-signature:1.0.0', + segtax: , + tokenTtl: 5, } } ] diff --git a/src/adloader.js b/src/adloader.js index c73f83f300c..db128c6d7ba 100644 --- a/src/adloader.js +++ b/src/adloader.js @@ -10,8 +10,7 @@ const _approvedLoadExternalJSList = [ 'adagio', 'browsi', 'brandmetrics', - 'justtag', - 'akamaidap' + 'justtag' ] /** diff --git a/test/spec/modules/akamaiDapRtdProvider_spec.js b/test/spec/modules/akamaiDapRtdProvider_spec.js index 25688abd99e..b350c2bb529 100644 --- a/test/spec/modules/akamaiDapRtdProvider_spec.js +++ b/test/spec/modules/akamaiDapRtdProvider_spec.js @@ -1,14 +1,13 @@ import {config} from 'src/config.js'; -import { - dapUtils, - generateRealTimeData, - akamaiDapRtdSubmodule, - storage, DAP_MAX_RETRY_TOKENIZE, DAP_SS_ID, DAP_TOKEN, DAP_MEMBERSHIP, DAP_ENCRYPTED_MEMBERSHIP, -} from 'modules/akamaiDapRtdProvider.js'; +import {SEGMENTS_STORAGE_KEY, TOKEN_STORAGE_KEY, dapUtils, addRealTimeData, getRealTimeData, akamaiDapRtdSubmodule, storage} from 'modules/akamaiDapRtdProvider.js'; import {server} from 'test/mocks/xhr.js'; +import logMessage from 'src/utils.js' const responseHeader = {'Content-Type': 'application/json'}; describe('akamaiDapRtdProvider', function() { + let getDataFromLocalStorageStub; + let getDapTokenStub; + const testReqBidsConfigObj = { adUnits: [ { @@ -19,21 +18,7 @@ describe('akamaiDapRtdProvider', function() { const onDone = function() { return true }; - const sampleGdprConsentConfig = { - 'gdpr': { - 'consentString': null, - 'vendorData': {}, - 'gdprApplies': true - } - }; - - const sampleUspConsentConfig = { - 'usp': '1YYY' - }; - - const sampleIdentity = { - type: 'dap-signature:1.0.0' - }; + const onSuccess = function() { return ('request', 200, 'success') }; const cmoduleConfig = { 'name': 'dap', @@ -43,19 +28,8 @@ describe('akamaiDapRtdProvider', function() { 'apiVersion': 'x1', 'domain': 'prebid.org', 'identityType': 'dap-signature:1.0.0', - 'segtax': 503 - } - } - - const emoduleConfig = { - 'name': 'dap', - 'waitForIt': true, - 'params': { - 'apiHostname': 'prebid.dap.akadns.net', - 'apiVersion': 'x1', - 'domain': 'prebid.org', - 'identityType': 'dap-signature:1.0.0', - 'segtax': 504 + 'segtax': 503, + 'tokenTtl': 5 } } @@ -63,119 +37,81 @@ describe('akamaiDapRtdProvider', function() { 'api_hostname': 'prebid.dap.akadns.net', 'api_version': 'x1', 'domain': 'prebid.org', - 'segtax': 503, - 'identity': sampleIdentity - } - - const esampleConfig = { - 'api_hostname': 'prebid.dap.akadns.net', - 'api_version': 'x1', - 'domain': 'prebid.org', - 'segtax': 504, - 'identity': sampleIdentity + 'segtax': 503 } - let cacheExpiry = Math.round(Date.now() / 1000.0) + 300; // in seconds - const sampleCachedToken = {'expires_at': cacheExpiry, 'token': 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIn0..6buzBd2BjtgoyaNbHN8YnQ.l38avCfm3sYNy798-ETYOugz0cOx1cCkjACkAhYszxzrZ0sUJ0AiF-NdDXVTiTyp2Ih3vCWKzS0rKJ8lbS1zhyEVWVu91QwtwseM2fBbwA5ggAgBEo5wV-IXqDLPxVnxsPF0D3hP6cNCiH9Q2c-vULfsLhMhG5zvvZDPBbn4hUY5fKB8LoCBTF9rbuuWGYK1nramnb4AlS5UK82wBsHQea1Ou_Kp5wWCMNZ6TZk5qKIuRBfPIAhQblWvHECaHXkg1wyoM9VASs_yNhne7RR-qkwzbFiPFiMJibNOt9hF3_vPDJO5-06ZBjRTP1BllYGWxI-uQX6InzN18Wtun2WHqg.63sH0SNlIRcsK57v0pMujfB_nhU8Y5CuQbsHqH5MGoM'}; - const cachedEncryptedMembership = {'expires_at': cacheExpiry, 'encryptedSegments': 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoic29tZXNlY3JldGludmF1bHQifQ..IvnIUQDqWBVYIS0gbcE9bw.Z4NZGvtogWaWlGH4e-GdYKe_PUc15M2x3Bj85rMWsN1A17mIxQIMOfg2hsQ2tgieLu5LggWPmsFu1Wbph6P0k3kOu1dVReoIhOHzxw50rP0DLHKaEZ5mLMJ7Lcosvwh4miIfFuCHlsX7J0sFgOTAp0zGo1S_UsHLtev1JflhjoSB0AoX95ALbAnyctirPuLJM8gZ1vXTiZ01jpvucGyR1lM4cWjPOeD8jPtgwaPGgSRZXE-3X2Cqy7z4Giam5Uqu74LPWTBuKtUQTGyAXA5QJoP7xwTbsU4O1f69lu3fWNqC92GijeTH1A4Zd_C-WXxWuQlDEURjlkWQoaqTHka2OqlnwukEQIf_v0r5KQQX64CTLhEUH91jeD0-E9ClcIP7pwOLxxqiKoaBmx8Mrnm_6Agj5DtTA1rusy3AL63sI_rsUxrmLrVt0Wft4aCfRkW8QpQxu8clFdOmce0NNCGeBCyCPVw9d9izrILlXJ6rItU2cpFrcbz8uw2otamF5eOFCOY3IzHedWVNNuKHFIUVC_xYSlsYvQ8f2QIP1eiMbmukcuPzmTzjw1h1_7IKaj-jJkXrnrY-TdDgX_4-_Z3rmbpXK2yTR7dBrsg-ubqFbgbKic1b4zlQEO_LbBlgPl3DYdWEuJ8CY2NUt1GfpATQGsufS2FTY1YGw_gkPe3q04l_cgLafDoxHvHh_t_0ZgPjciW82gThB_kN4RP7Mc3krVcXl_P6N1VbV07xyx0hCyVsrrxbLslI8q9wYDiLGci7mNmByM5j7SXV9jPwwPkHtn0HfMJlw2PFbIDPjgG3h7sOyLcBIJTTvuUIgpHPIkRWLIl_4FlIucXbJ7orW2nt5BWleBVHgumzGcnl9ZNcZb3W-dsdYPSOmuj0CY28MRTP2oJ1rzLInbDDpIRffJBtR7SS4nYyy7Vi09PtBigod5YNz1Q0WDSJxr8zeH_aKFaXInw7Bfo_U0IAcLiRgcT0ogsMLeQRjRFy27mr4XNJv3NtHhbdjDAwF2aClCktXyXbQaVdsPH2W71v6m2Q9rB5GQWOktw2s5f-4N1-_EBPGq6TgjF-aJZP22MJVwp1pimT50DfOzoeEqDwi862NNwNNoHmcObH0ZfwAXlhRxsgupNBe20-MNNABj2Phlfv4DUrtQbMdfCnNiypzNCmoTb7G7c_o5_JUwoV_GVkwUtvmi_IUm05P4GeMASSUw8zDKVRAj9h31C2cabM8RjMHGhkbCWpUP2pcz9zlJ7Y76Dh3RLnctfTw7DG9U4w4UlaxNZOgLUiSrGwfyapuSiuGUpuOJkBBLiHmEqAGI5C8oJpcVRccNlHxJAYowgXyFopD5Fr-FkXmv8KMkS0h5C9F6KihmDt5sqDD0qnjM0hHJgq01l7wjVnhEmPpyD-6auFQ-xDnbh1uBOJ_0gCVbRad--FSa5p-dXenggegRxOvZXJ0iAtM6Fal5Og-RCjexIHa9WhVbXhQBJpkSTWwAajZJ64eQ.yih49XB51wE-Xob7COT9OYqBrzBmIMVCQbLFx2UdzkI'}; - const cachedMembership = {'expires_at': cacheExpiry, 'said': 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIn0..QwvU5h0NVJYaJbs5EqWCKA.XNaJHSlnsH8P-yBIr3gIEqavLONWDIFyj7QCHFwJVkwXH_EYkxrk0_26b0uMPzfJp5URnqxKZusMH9DzEJsmj8EMrKQv1y3IYYMsW5_0BdP5bcAWfG6fzOqtMOwLiYRkYiQOqn1ZVGzhovheHWEmNr2_oCY0LvAr3iN1eG_K-l-bBKvBWnwvuuGKquUfCqO8NMMq6wtkecEXM9blqFRZ7oNYmW2aIG7qcHUsrUW7HMr9Ev2Ik0sIeEUsOYrgf_X_VA64RgKSTRugS9FupMv1p54JkHokwduF9pOFmW8QLQi8itFogKGbbgvOTNnmahxQUX5FcrjjYLqHwKqC8htLdlHnO5LWU9l4A7vLXrRurvoSnh0cAJy0GsdoyEwTqR9bwVFHoPquxlJjQ4buEd7PIxpBj9Qg9oOPH3b2upbMTu5CQ9oj526eXPhP5G54nwGklm2AZ3Vggd7jCQJn45Jjiq0iIfsXAtpqS2BssCLBN8WhmUTnStK8m5sux6WUBdrpDESQjPj-EEHVS-DB5rA7icRUh6EzRxzen2rndvHvnwVhSG_l6cwPYuJ0HE0KBmYHOoqNpKwzoGiKFHrf4ReA06iWB3V2TEGJucGujhtQ9_18WwHCeJ1XtQiiO1eqa3tp5MwAbFXawVFl3FFOBgadrPyvGmkmUJ6FCLU2MSwHiYZmANMnJsokFX_6DwoAgO3U_QnvEHIVSvefc7ReeJ8fBDdmrH3LtuLrUpXsvLvEIMQdWQ_SXhjKIi7tOODR8CfrhUcdIjsp3PZs1DpuOcDB6YJKbGnKZTluLUJi3TyHgyi-DHXdTm-jSE5i_DYJGW-t2Gf23FoQhexv4q7gdrfsKfcRJNrZLp6Gd6jl4zHhUtY.nprKBsy9taQBk6dCPbA7BFF0CiGhQOEF_MazZ2bedqk', 'cohorts': ['9', '11', '13']}; - const rtdUserObj = { - name: 'www.dataprovider3.com', - ext: { - taxonomyname: 'iab_audience_taxonomy' - }, - segment: [ - { - id: '1918' - }, - { - id: '1939' - } - ] - }; - - const encRtdUserObj = { - name: 'www.dataprovider3.com', - ext: { - segtax: 504, - taxonomyname: 'iab_audience_taxonomy' - }, - segment: [] - }; - - const cachedRtd = { - rtd: { - ortb2: { - user: { - data: [rtdUserObj] - } - } - } - }; - - let membership = { - said: cachedMembership.said, - cohorts: cachedMembership.cohorts, - attributes: null - }; - let encMembership = { - encryptedSegments: cachedEncryptedMembership.encryptedSegments - }; - encRtdUserObj.segment.push({ id: encMembership.encryptedSegments }); - const cachedEncRtd = { - rtd: { - ortb2: { - user: { - data: [encRtdUserObj] - } - } - } + const sampleIdentity = { + type: 'dap-signature:1.0.0' }; beforeEach(function() { config.resetConfig(); - storage.removeDataFromLocalStorage(DAP_TOKEN); - storage.removeDataFromLocalStorage(DAP_MEMBERSHIP); - storage.removeDataFromLocalStorage(DAP_ENCRYPTED_MEMBERSHIP); - storage.removeDataFromLocalStorage(DAP_SS_ID); + getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage') }); afterEach(function () { + getDataFromLocalStorageStub.restore(); }); describe('akamaiDapRtdSubmodule', function() { it('successfully instantiates', function () { - expect(akamaiDapRtdSubmodule.init()).to.equal(true); + expect(akamaiDapRtdSubmodule.init()).to.equal(true); }); }); describe('Get Real-Time Data', function() { it('gets rtd from local storage cache', function() { + const rtdConfig = { + params: { + segmentCache: true + } + }; + const bidConfig = {}; - storage.setDataInLocalStorage(DAP_TOKEN, JSON.stringify(sampleCachedToken)); - let dapGetMembershipFromLocalStorageStub = sinon.stub(dapUtils, 'dapGetMembershipFromLocalStorage').returns(membership) - let dapGetRtdObjStub = sinon.stub(dapUtils, 'dapGetRtdObj').returns(cachedRtd) - let dapGetEncryptedMembershipFromLocalStorageStub = sinon.stub(dapUtils, 'dapGetEncryptedMembershipFromLocalStorage').returns(encMembership) - let dapGetEncryptedRtdObjStub = sinon.stub(dapUtils, 'dapGetEncryptedRtdObj').returns(cachedEncRtd) + + const rtdUserObj1 = { + name: 'www.dataprovider3.com', + ext: { + taxonomyname: 'iab_audience_taxonomy' + }, + segment: [ + { + id: '1918' + }, + { + id: '1939' + } + ] + }; + + const cachedRtd = { + rtd: { + ortb2: { + user: { + data: [rtdUserObj1] + } + } + } + }; + + getDataFromLocalStorageStub.withArgs(SEGMENTS_STORAGE_KEY).returns(JSON.stringify(cachedRtd)); expect(config.getConfig().ortb2).to.be.undefined; - generateRealTimeData(bidConfig, () => {}, emoduleConfig, {}); - expect(config.getConfig().ortb2.user.data).to.deep.include.members([encRtdUserObj]); - generateRealTimeData(bidConfig, () => {}, cmoduleConfig, {}); - expect(config.getConfig().ortb2.user.data).to.deep.include.members([rtdUserObj]); - dapGetRtdObjStub.restore() - dapGetMembershipFromLocalStorageStub.restore() - dapGetEncryptedRtdObjStub.restore() - dapGetEncryptedMembershipFromLocalStorageStub.restore() + getRealTimeData(bidConfig, () => {}, rtdConfig, {}); + expect(config.getConfig().ortb2.user.data).to.deep.include.members([rtdUserObj1]); + }); + + it('should initalise and return with config', function () { + expect(getRealTimeData(testReqBidsConfigObj, onDone, cmoduleConfig)).to.equal(undefined) }); }); describe('dapTokenize', function () { it('dapTokenize error callback', function () { let configAsync = JSON.parse(JSON.stringify(sampleConfig)); - let submoduleCallback = dapUtils.dapTokenize(configAsync, sampleIdentity, onDone, - function(token, status, xhr, onDone) { + let submoduleCallback = dapUtils.dapTokenize(configAsync, sampleIdentity, + function(token, status, xhr) { }, - function(xhr, status, error, onDone) { + function(xhr, status, error) { } ); let request = server.requests[0]; @@ -185,10 +121,10 @@ describe('akamaiDapRtdProvider', function() { it('dapTokenize success callback', function () { let configAsync = JSON.parse(JSON.stringify(sampleConfig)); - let submoduleCallback = dapUtils.dapTokenize(configAsync, sampleIdentity, onDone, - function(token, status, xhr, onDone) { + let submoduleCallback = dapUtils.dapTokenize(configAsync, sampleIdentity, + function(token, status, xhr) { }, - function(xhr, status, error, onDone) { + function(xhr, status, error) { } ); let request = server.requests[0]; @@ -199,54 +135,40 @@ describe('akamaiDapRtdProvider', function() { describe('dapTokenize and dapMembership incorrect params', function () { it('Onerror and config are null', function () { - expect(dapUtils.dapTokenize(null, 'identity', onDone, null, null)).to.be.equal(undefined); - expect(dapUtils.dapMembership(null, 'identity', onDone, null, null)).to.be.equal(undefined); - expect(dapUtils.dapEncryptedMembership(null, 'identity', onDone, null, null)).to.be.equal(undefined); + expect(dapUtils.dapTokenize(null, 'identity', null, null)).to.be.equal(undefined); + expect(dapUtils.dapMembership(null, 'identity', null, null)).to.be.equal(undefined); const config = { 'api_hostname': 'prebid.dap.akadns.net', 'api_version': 1, 'domain': '', 'segtax': 503 }; - const encConfig = { - 'api_hostname': 'prebid.dap.akadns.net', - 'api_version': 1, - 'domain': '', - 'segtax': 504 - }; let identity = { type: 'dap-signature:1.0.0' }; - expect(dapUtils.dapTokenize(config, identity, onDone, null, null)).to.be.equal(undefined); - expect(dapUtils.dapMembership(config, 'token', onDone, null, null)).to.be.equal(undefined); - expect(dapUtils.dapEncryptedMembership(encConfig, 'token', onDone, null, null)).to.be.equal(undefined); - }); - }); - - describe('Getting dapTokenize, dapMembership and dapEncryptedMembership from localstorage', function () { - it('dapGetTokenFromLocalStorage success', function () { - storage.setDataInLocalStorage(DAP_TOKEN, JSON.stringify(sampleCachedToken)); - expect(dapUtils.dapGetTokenFromLocalStorage(60)).to.be.equal(sampleCachedToken.token); + expect(dapUtils.dapTokenize(config, identity, null, null)).to.be.equal(undefined); + expect(dapUtils.dapMembership(config, 'token', null, null)).to.be.equal(undefined); }); - it('dapGetMembershipFromLocalStorage success', function () { - storage.setDataInLocalStorage(DAP_MEMBERSHIP, JSON.stringify(cachedMembership)); - expect(JSON.stringify(dapUtils.dapGetMembershipFromLocalStorage())).to.be.equal(JSON.stringify(membership)); - }); - - it('dapGetEncryptedMembershipFromLocalStorage success', function () { - storage.setDataInLocalStorage(DAP_ENCRYPTED_MEMBERSHIP, JSON.stringify(cachedEncryptedMembership)); - expect(JSON.stringify(dapUtils.dapGetEncryptedMembershipFromLocalStorage())).to.be.equal(JSON.stringify(encMembership)); + it('dapGetToken success', function () { + let dapTokenizeStub = sinon.stub(dapUtils, 'dapTokenize').returns(onSuccess); + expect(dapUtils.dapGetToken(sampleConfig, 'token', + function(token, status, xhr) { + }, + function(xhr, status, error) { + } + )).to.be.equal(null); + dapTokenizeStub.restore(); }); }); describe('dapMembership', function () { it('dapMembership success callback', function () { let configAsync = JSON.parse(JSON.stringify(sampleConfig)); - let submoduleCallback = dapUtils.dapMembership(configAsync, 'token', onDone, - function(token, status, xhr, onDone) { + let submoduleCallback = dapUtils.dapMembership(configAsync, 'token', + function(token, status, xhr) { }, - function(xhr, status, error, onDone) { + function(xhr, status, error) { } ); let request = server.requests[0]; @@ -256,38 +178,10 @@ describe('akamaiDapRtdProvider', function() { it('dapMembership error callback', function () { let configAsync = JSON.parse(JSON.stringify(sampleConfig)); - let submoduleCallback = dapUtils.dapMembership(configAsync, 'token', onDone, - function(token, status, xhr, onDone) { - }, - function(xhr, status, error, onDone) { - } - ); - let request = server.requests[0]; - request.respond(400, responseHeader, JSON.stringify('error')); - expect(submoduleCallback).to.equal(undefined); - }); - }); - - describe('dapEncMembership', function () { - it('dapEncMembership success callback', function () { - let configAsync = JSON.parse(JSON.stringify(esampleConfig)); - let submoduleCallback = dapUtils.dapEncryptedMembership(configAsync, 'token', onDone, - function(token, status, xhr, onDone) { - }, - function(xhr, status, error, onDone) { - } - ); - let request = server.requests[0]; - request.respond(200, responseHeader, JSON.stringify('success')); - expect(submoduleCallback).to.equal(undefined); - }); - - it('dapEncMembership error callback', function () { - let configAsync = JSON.parse(JSON.stringify(esampleConfig)); - let submoduleCallback = dapUtils.dapEncryptedMembership(configAsync, 'token', onDone, - function(token, status, xhr, onDone) { + let submoduleCallback = dapUtils.dapMembership(configAsync, 'token', + function(token, status, xhr) { }, - function(xhr, status, error, onDone) { + function(xhr, status, error) { } ); let request = server.requests[0]; @@ -298,243 +192,55 @@ describe('akamaiDapRtdProvider', function() { describe('dapMembership', function () { it('should invoke the getDapToken and getDapMembership', function () { + let config = { + api_hostname: cmoduleConfig.params.apiHostname, + api_version: cmoduleConfig.params.apiVersion, + domain: cmoduleConfig.params.domain, + segtax: cmoduleConfig.params.segtax + }; + let identity = { + type: cmoduleConfig.params.identityType + }; + let membership = { said: 'item.said1', cohorts: 'item.cohorts', attributes: null }; - let getDapMembershipStub = sinon.stub(dapUtils, 'dapGetMembershipFromLocalStorage').returns(membership); - generateRealTimeData(testReqBidsConfigObj, onDone, cmoduleConfig); + let getDapTokenStub = sinon.stub(dapUtils, 'dapGetToken').returns('token3'); + let getDapMembershipStub = sinon.stub(dapUtils, 'dapGetMembership').returns(membership); + let dapTokenizeStub = sinon.stub(dapUtils, 'dapTokenize').returns('response', 200, 'request'); + getRealTimeData(testReqBidsConfigObj, onDone, cmoduleConfig); + expect(getDapTokenStub.calledOnce).to.be.equal(true); expect(getDapMembershipStub.calledOnce).to.be.equal(true); + getDapTokenStub.restore(); getDapMembershipStub.restore(); + dapTokenizeStub.restore(); }); }); - describe('dapEncMembership test', function () { - it('should invoke the getDapToken and getEncDapMembership', function () { - let encMembership = { - encryptedSegments: 'enc.seg', + describe('dapMembershipToRtbSegment', function () { + it('dapMembershipToRtbSegment', function () { + let membership1 = { + said: 'item.said1', + cohorts: 'item.cohorts', + attributes: null }; - - let getDapEncMembershipStub = sinon.stub(dapUtils, 'dapGetEncryptedMembershipFromLocalStorage').returns(encMembership); - generateRealTimeData(testReqBidsConfigObj, onDone, emoduleConfig); - expect(getDapEncMembershipStub.calledOnce).to.be.equal(true); - getDapEncMembershipStub.restore(); - }); - }); - - describe('dapGetRtdObj test', function () { - it('dapGetRtdObj', function () { const config = { apiHostname: 'prebid.dap.akadns.net', apiVersion: 'x1', domain: 'prebid.org', + tokenTtl: 5, segtax: 503 }; - expect(dapUtils.dapRefreshMembership(config, 'token', onDone)).to.equal(undefined) - const membership = {cohorts: ['1', '5', '7']} - expect(dapUtils.dapGetRtdObj(membership, config.segtax)).to.not.equal(undefined); - }); - }); - - describe('checkAndAddRealtimeData test', function () { - it('add realtime data for segtax 503 and 504', function () { - dapUtils.checkAndAddRealtimeData(cachedEncRtd, 504); - dapUtils.checkAndAddRealtimeData(cachedEncRtd, 504); - expect(config.getConfig().ortb2.user.data).to.deep.include.members([encRtdUserObj]); - dapUtils.checkAndAddRealtimeData(cachedRtd, 503); - expect(config.getConfig().ortb2.user.data).to.deep.include.members([rtdUserObj]); - }); - }); - - describe('dapExtractExpiryFromToken test', function () { - it('test dapExtractExpiryFromToken function', function () { - let tokenWithoutExpiry = 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIn0..6buzBd2BjtgoyaNbHN8YnQ.l38avCfm3sYNy798-ETYOugz0cOx1cCkjACkAhYszxzrZ0sUJ0AiF-NdDXVTiTyp2Ih3vCWKzS0rKJ8lbS1zhyEVWVu91QwtwseM2fBbwA5ggAgBEo5wV-IXqDLPxVnxsPF0D3hP6cNCiH9Q2c-vULfsLhMhG5zvvZDPBbn4hUY5fKB8LoCBTF9rbuuWGYK1nramnb4AlS5UK82wBsHQea1Ou_Kp5wWCMNZ6TZk5qKIuRBfPIAhQblWvHECaHXkg1wyoM9VASs_yNhne7RR-qkwzbFiPFiMJibNOt9hF3_vPDJO5-06ZBjRTP1BllYGWxI-uQX6InzN18Wtun2WHqg.63sH0SNlIRcsK57v0pMujfB_nhU8Y5CuQbsHqH5MGoM' - expect(dapUtils.dapExtractExpiryFromToken(tokenWithoutExpiry)).to.equal(undefined); - let tokenWithExpiry = 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIiwiZXhwIjoxNjQzODMwMzY5fQ..hTbcSQgmmO0HUJJrQ5fRHw.7zjrQXNNVkb-GD0ZhIVhEPcWbyaDBilHTWv-bp1lFZ9mdkSC0QbcAvUbYteiTD7ya23GUwcL2WOW8WgRSHaWHOJe0B5NDqfdUGTzElWfu7fFodRxRgGmwG8Rq5xxteFKLLGHLf1mFYRJKDtjtgajGNUKIDfn9AEt-c5Qz4KU8VolG_KzrLROx-f6Z7MnoPTcwRCj0WjXD6j2D6RAZ80-mKTNIsMIELdj6xiabHcjDJ1WzwtwCZSE2y2nMs451pSYp8W-bFPfZmDDwrkjN4s9ASLlIXcXgxK-H0GsiEbckQOZ49zsIKyFtasBvZW8339rrXi1js-aBh99M7aS5w9DmXPpUDmppSPpwkeTfKiqF0cQiAUq8tpeEQrGDJuw3Qt2.XI8h9Xw-VZj_NOmKtV19wLM63S4snos7rzkoHf9FXCw' - expect(dapUtils.dapExtractExpiryFromToken(tokenWithExpiry)).to.equal(1643830369); - }); - }); - - describe('dapRefreshToken test', function () { - it('test dapRefreshToken success response', function () { - dapUtils.dapRefreshToken(sampleConfig, true, onDone) - let request = server.requests[0]; - responseHeader['Akamai-DAP-Token'] = sampleCachedToken.token; - request.respond(200, responseHeader, JSON.stringify(sampleCachedToken.token)); - expect(JSON.parse(storage.getDataFromLocalStorage(DAP_TOKEN)).token).to.be.equal(sampleCachedToken.token); - }); - - it('test dapRefreshToken success response with deviceid 100', function () { - dapUtils.dapRefreshToken(esampleConfig, true, onDone) - let request = server.requests[0]; - responseHeader['Akamai-DAP-100'] = sampleCachedToken.token; - request.respond(200, responseHeader, ''); - expect(storage.getDataFromLocalStorage('dap_deviceId100')).to.be.equal(sampleCachedToken.token); - }); - - it('test dapRefreshToken success response with exp claim', function () { - dapUtils.dapRefreshToken(sampleConfig, true, onDone) - let request = server.requests[0]; - let tokenWithExpiry = 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIiwiZXhwIjoxNjQzODMwMzY5fQ..hTbcSQgmmO0HUJJrQ5fRHw.7zjrQXNNVkb-GD0ZhIVhEPcWbyaDBilHTWv-bp1lFZ9mdkSC0QbcAvUbYteiTD7ya23GUwcL2WOW8WgRSHaWHOJe0B5NDqfdUGTzElWfu7fFodRxRgGmwG8Rq5xxteFKLLGHLf1mFYRJKDtjtgajGNUKIDfn9AEt-c5Qz4KU8VolG_KzrLROx-f6Z7MnoPTcwRCj0WjXD6j2D6RAZ80-mKTNIsMIELdj6xiabHcjDJ1WzwtwCZSE2y2nMs451pSYp8W-bFPfZmDDwrkjN4s9ASLlIXcXgxK-H0GsiEbckQOZ49zsIKyFtasBvZW8339rrXi1js-aBh99M7aS5w9DmXPpUDmppSPpwkeTfKiqF0cQiAUq8tpeEQrGDJuw3Qt2.XI8h9Xw-VZj_NOmKtV19wLM63S4snos7rzkoHf9FXCw' - responseHeader['Akamai-DAP-Token'] = tokenWithExpiry; - request.respond(200, responseHeader, JSON.stringify(tokenWithExpiry)); - expect(JSON.parse(storage.getDataFromLocalStorage(DAP_TOKEN)).expires_at).to.be.equal(1643830359); - }); - - it('test dapRefreshToken error response', function () { - storage.setDataInLocalStorage(DAP_TOKEN, JSON.stringify(sampleCachedToken)); - dapUtils.dapRefreshToken(sampleConfig, false, onDone) - let request = server.requests[0]; - request.respond(400, responseHeader, 'error'); - expect(JSON.parse(storage.getDataFromLocalStorage(DAP_TOKEN)).expires_at).to.be.equal(cacheExpiry);// Since the expiry is same, the token is not updated in the cache - }); - }); - - describe('dapRefreshMembership test', function () { - it('test dapRefreshMembership success response', function () { - let membership = {'cohorts': ['9', '11', '13'], 'said': 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIn0..17wnrhz6FbWx0Cf6LXpm1A.m9PKVCradk3CZokNKzVHzE06TOqiXYeijgxTQUiQy5Syx-yicnO8DyYX6zQ6rgPcNgUNRt4R4XE5MXuK0laUVQJr9yc9g3vUfQfw69OMYGW_vRlLMPzoNOhF2c4gSyfkRrLr7C0qgALmZO1D11sPflaCTNmO7pmZtRaCOB5buHoWcQhp1bUSJ09DNDb31dX3llimPwjNGSrUhyq_EZl4HopnnjxbM4qVNMY2G_43C_idlVOvbFoTxcDRATd-6MplJoIOIHQLDZEetpIOVcbEYN9gQ_ndBISITwuu5YEgs5C_WPHA25nm6e4BT5R-tawSA8yPyQAupqE8gk4ZWq_2-T0cqyTstIHrMQnZ_vysYN7h6bkzE-KeZRk7GMtySN87_fiu904hLD9QentGegamX6UAbVqQh7Htj7SnMHXkEenjxXAM5mRqQvNCTlw8k-9-VPXs-vTcKLYP8VFf8gMOmuYykgWac1gX-svyAg-24mo8cUbqcsj9relx4Qj5HiXUVyDMBZxK-mHZi-Xz6uv9GlggcsjE13DSszar-j2OetigpdibnJIxRZ-4ew3-vlvZ0Dul3j0LjeWURVBWYWfMjuZ193G7lwR3ohh_NzlNfwOPBK_SYurdAnLh7jJgTW-lVLjH2Dipmi9JwX9s03IQq9opexAn7hlM9oBI6x5asByH8JF8WwZ5GhzDjpDwpSmHPQNGFRSyrx_Sh2CPWNK6C1NJmLkyqAtJ5iw0_al7vPDQyZrKXaLTjBCUnbpJhUZ8dUKtWLzGPjzFXp10muoDIutd1NfyKxk1aWGhx5aerYuLdywv6cT_M8RZTi8924NGj5VA30V5OvEwLLyX93eDhntXZSCbkPHpAfiRZNGXrPY.GhCbWGQz11mIRD4uPKmoAuFXDH7hGnils54zg7N7-TU'} - dapUtils.dapRefreshMembership(sampleConfig, sampleCachedToken.token, onDone) - let request = server.requests[0]; - request.respond(200, responseHeader, JSON.stringify(membership)); - let rtdObj = dapUtils.dapGetRtdObj(membership, 503) - expect(config.getConfig().ortb2.user.data).to.deep.include.members(rtdObj.rtd.ortb2.user.data); - }); - - it('test dapRefreshMembership success response with exp claim', function () { - let membership = {'cohorts': ['9', '11', '13'], 'said': 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIiwiZXhwIjoxNjQ3OTcxNTU4fQ..ptdM5WO-62ypXlKxFXD4FQ.waEo9MHS2NYQCi-zh_p6HgT9BdqGyQbBq4GfGLfsay4nRBgICsTS-VkV6e7xx5U1T8BgpKkRJIZBwTOY5Pkxk9FpK5nnffDSEljRrp1LXLCkNP4qwrlqHInFbZsonNWW4_mW-7aUPlTwIsTbfjTuyHdXHeQa1ALrwFFFWE7QUmPNd2RsHjDwUsxlJPEb5TnHn5W0Mgo_PQZaxvhJInMbxPgtJLoqnJvOqCBEoQY7au7ALZL_nWK8XIwPMF19J7Z3cBg9vQInhr_E3rMdQcAFHEzYfgoNcIYCCR0t1UOqUE3HNtX-E64kZAYKWdlsBb9eW5Gj9hHYyPNL_4Hntjg5eLXGpsocMg0An-qQKGC6hkrxKzeM-GrjpvSaQLNs4iqDpHUtzA02LW_vkLkMNRUiyXVJ3FUZwfyq6uHSRKWZ6UFdAfL0rfJ8q8x8Ll-qJO2Jfyvidlsi9FIs7x1WJrvDCKepfAQM1UXRTonrQljFBAk83PcL2bmWuJDgJZ0lWS4VnZbIf6A7fDourmkDxdVRptvQq5nSjtzCA6whRw0-wGz8ehNJsaJw9H_nG9k4lRKs7A5Lqsyy7TVFrAPjnA_Q1a2H6xF2ULxrtIqoNqdX7k9RjowEZSQlZgZUOAmI4wzjckdcSyC_pUlYBMcBwmlld34mmOJe9EBHAxjdci7Q_9lvj1HTcwGDcQITXnkW9Ux5Jkt9Naw-IGGrnEIADaT2guUAto8W_Gb05TmwHSd6DCmh4zepQCbqeVe6AvPILtVkTgsTTo27Q-NvS7h-XtthJy8425j5kqwxxpZFJ0l0ytc6DUyNCLJXuxi0JFU6-LoSXcROEMVrHa_Achufr9vHIELwacSAIHuwseEvg_OOu1c1WYEwZH8ynBLSjqzy8AnDj24hYgA0YanPAvDqacrYrTUFqURbHmvcQqLBTcYa_gs7uDx4a1EjtP_NvHRlvCgGAaASrjGMhTX8oJxlTqahhQ.pXm-7KqnNK8sbyyczwkVYhcjgiwkpO8LjBBVw4lcyZE'} - dapUtils.dapRefreshMembership(sampleConfig, sampleCachedToken.token, onDone) - let request = server.requests[0]; - request.respond(200, responseHeader, JSON.stringify(membership)); - let rtdObj = dapUtils.dapGetRtdObj(membership, 503) - expect(config.getConfig().ortb2.user.data).to.deep.include.members(rtdObj.rtd.ortb2.user.data); - expect(JSON.parse(storage.getDataFromLocalStorage(DAP_MEMBERSHIP)).expires_at).to.be.equal(1647971548); - }); - - it('test dapRefreshMembership 400 error response', function () { - dapUtils.dapRefreshMembership(sampleConfig, sampleCachedToken.token, onDone) - let request = server.requests[0]; - request.respond(400, responseHeader, 'error'); - expect(config.getConfig().ortb2).to.be.equal(undefined); - }); - - it('test dapRefreshMembership 403 error response', function () { - dapUtils.dapRefreshMembership(sampleConfig, sampleCachedToken.token, onDone) - let request = server.requests[0]; - request.respond(403, responseHeader, 'error'); - let requestTokenize = server.requests[1]; - responseHeader['Akamai-DAP-Token'] = sampleCachedToken.token; - requestTokenize.respond(200, responseHeader, ''); - let requestMembership = server.requests[2]; - requestMembership.respond(403, responseHeader, 'error'); - expect(server.requests.length).to.be.equal(DAP_MAX_RETRY_TOKENIZE + 2); - }); - }); - - describe('dapRefreshEncryptedMembership test', function () { - it('test dapRefreshEncryptedMembership success response', function () { - let expiry = Math.round(Date.now() / 1000.0) + 3600; // in seconds - let encMembership = 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoic29tZXNlY3JldGludmF1bHQifQ..f8_At4OqeQXyQcSwThOJ_w.69ImVQ3bEZ6QP7ROCRpAJjNcKY49SEPYR6qTp_8l7L8kQdPbpi4wmuOzt78j7iBrX64k2wltzmQFjDmVKSxDhrEguxpgx6t-L1tT8ZA0UosMWpVsgmKEZxOn2e9ES3jw8RNCS4WSWocSPQX33xSb51evXjm9E1s0tGoLnwXl0GsUvzRsSU86wQG6RZnAQTi7s-r-M2TKibdDjUqgIt62vJ-aBZ7RWw91MINgOdmDNs1bFfbBX5Cy1kd4-kjvRDz_aJ6zHX4sK_7EmQhGEY3tW-A3_l2I88mw-RSJaPkb_IWg0QpVwXDaE2F2g8NpY1PzCRvG_NIE8r28eK5q44OMVitykHmKmBXGDj7z2JVgoXkfo5u0I-dypZARn4GP_7niK932avB-9JD7Mz3TrlU4GZ7IpYfJ91PMsRhrs5xNPQwLZbpuhF76A7Dp7iss71UjkGCiPTU6udfRb4foyf_7xEF66m1eQVcVaMdxEbMuu9GBfdr-d04TbtJhPfUV8JfxTenvRYoi13n0j5kH0M5OgaSQD9kQ3Mrd9u-Cms-BGtT0vf-N8AaFZY_wn0Y4rkpv5HEaH7z3iT4RCHINWrXb_D0WtjLTKQi2YmF8zMlzUOewNJGwZRwbRwxc7JoDIKEc5RZkJYevfJXOEEOPGXZ7AGZxOEsJawPqFqd_nOUosCZS4akHhcDPcVowoecVAV0hhhoS6JEY66PhPp1snbt6yqA-fQhch7z8Y-DZT3Scibvffww3Scg_KFANWp0KeEvHG0vyv9R2F4o66viSS8y21MDnM7Yjk8C-j7aNMldUQbjN_7Yq1nkfe0jiBX_hsINBRPgJHUY4zCaXuyXs-JZZfU92nwG0RT3A_3RP2rpY8-fXp9d3C2QJjEpnmHvTMsuAZCQSBe5DVrJwN_UKedxcJEoOt0wLz6MaCMyYZPd8tnQeqYK1cd3RgQDXtzKC0HDw1En489DqJXEst4eSSkaaW1lImLeaF8XCOaIqPqoyGk4_6KVLw5Q7OnpczuXqYKMd9UTMovGeuTuo1k0ddfEqTq9QwxkwZL51AiDRnwTCAeYBU1krV8FCJQx-mH_WPB5ftZj-o_3pbvANeRk27QBVmjcS-tgDllJkWBxX-4axRXzLw8pUUUZUT_NOL0OiqUCWVm0qMBEpgRQ57Se42-hkLMTzLhhGJOnVcaXU1j4ep-N7faNvbgREBjf_LgzvaWS90a2NJ9bB_J9FyXelhCN_AMLfdOS3fHkeWlZ0u0PMbn5DxXRMe0l9jB-2VJZhcPQRlWoYyoCO3l4F5ZmuQP5Xh9CU4tvSWih6jlwMDgdVWuTpdfPD5bx8ccog3JDq87enx-QtPzLU3gMgouNARJGgNwKS_GJSE1uPrt2oiqgZ3Z0u_I5MKvPdQPV3o-4rsaE730eB4OwAOF-mkGWpzy8Pbl-Qe5PR9mHBhuyJgZ-WDSCHl5yvet2kfO9mPXZlqBQ26fzTcUYH94MULAZn36og6w.3iKGv-Le-AvRmi26W1v6ibRLGbwKbCR92vs-a9t55hw'; - dapUtils.dapRefreshEncryptedMembership(esampleConfig, sampleCachedToken.token, onDone) - let request = server.requests[0]; - responseHeader['Akamai-DAP-Token'] = encMembership; - request.respond(200, responseHeader, encMembership); - let rtdObj = dapUtils.dapGetEncryptedRtdObj({'encryptedSegments': encMembership}, 504) - expect(config.getConfig().ortb2.user.data).to.deep.include.members(rtdObj.rtd.ortb2.user.data); - expect(JSON.parse(storage.getDataFromLocalStorage(DAP_ENCRYPTED_MEMBERSHIP)).expires_at).to.equal(expiry); - }); - - it('test dapRefreshEncryptedMembership success response with exp claim', function () { - let encMembership = 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoic29tZXNlY3JldGludmF1bHQiLCJleHAiOjE2NDM4MzA2NDB9..inYoxwht_aqTIWqGhEm_Gw.wDcCUOCwtqgnNUouaD723gKfm7X7bgkHgtiX4mr07P3tWk25PUQunmwTLhWBB5CYzzGIfIvveG_u4glNRLi_eRSQV4ihKKk1AN-BSSJ3d0CLAdY9I1WG5vX1VmopXyKnV90bl9SLNqnhg4Vxe6YU4ogTYxsKHuIN1EeIH4hpl-HbCQWQ1DQt4mB-MQF8V9AWTfU0D7sFMSK8f9qj6NGmf1__oHdHUlws0t5V2UAn_dhJexsuREK_gh65pczCuly5eEcziZ82LeP-nOhKWSRHB_tS_mKXrRU6_At_EVDgtfA3PSBJ6eQylCii6bTL42vZzz4jZhJv_3eLfRdKqpVT5CWNBzcDoQ2VcQgKgIBtPJ45KFfAYTQ6kdl21QMSjqtu8GTsv1lEZtrqHY6zRiG8_Mu28-PmjEw4LDdZmBDOeroue_MJD6wuE_jlE7J2iVdo8CkVnoRgzFwNbKBo7CK4z0WahV9rhuOm0LKAN5H0jF_gj696U-3fVTDTIb8ndNKNI2_xAhvWs00BFGtUtWgr8QGDGRTDCNGsDgnb_Vva9xCqVOyAE9O3Fq1QYl-tMA-KkBt3zzvmFFpOxpOyH-lUubKLKlsrxKc3GSyVEQ9DDLhrXXJgR5H5BSE4tjlK7p3ODF5qz0FHtIj7oDcgLazFO7z2MuFy2LjJmd3hKl6ujcfYEDiQ4D3pMIo7oiU33aFBD1YpzI4-WzNfJlUt1FoK0-DAXpbbV95s8p08GOD4q81rPw5hRADKJEr0QzrbDwplTWCzT2fKXMg_dIIc5AGqGKnVRUS6UyF1DnHpudNIJWxyWZjWIEw_QNjU0cDFmyPSyKxNrnfq9w8WE2bfbS5KTicxei5QHnC-cnL7Nh7IXp7WOW6R1YHbNPT7Ad4OhnlV-jjrXwkSv4wMAbfwAWoSCchGh7uvENNAeJymuponlJbOgw_GcYM73hMs8Z8W9qxRfbyF4WX5fDKXg61mMlaieHkc0EnoC5q7uKyXuZUehHZ76JLDFmewslLkQq5SkVCttzJePBnY1ouPEHw5ZTzUnG5f01QQOVcjIN-AqXNDbG5IOwq0heyS6vVfq7lZKJdLDVQ21qRjazGPaqYwLzugkWkzCOzPTgyFdbXzgjfmJwylHSOM5Jpnul84GzxEQF-1mHP2A8wtIT-M7_iX24It2wwWvc8qLA6GEqruWCtNyoug8CXo44mKdSSCGeEZHtfMbzXdLIBHCy2jSHz5i8S7DU_R7rE_5Ssrb81CqIYbgsAQBHtOYoyvzduTOruWcci4De0QcULloqImIEHUuIe2lnYO889_LIx5p7nE3UlSvLBo0sPexavFUtHqI6jdG6ye9tdseUEoNBDXW0aWD4D-KXX1JLtAgToPVUtEaXCJI7QavwO9ZG6UZM6jbfuJ5co0fvUXp6qYrFxPQo2dYHkar0nT6s1Zg5l2g8yWlLUJrHdHAzAw_NScUp71OpM4TmNsLnYaPVPcOxMvtJXTanbNWr0VKc8gy9q3k_1XxAnQwiduNs7f5bA-6qCVpayHv5dE7mUhFEwyh1_w95jEaURsQF_hnnd2OqRkADfiok4ZiPU2b38kFW1LXjpI39XXES3JU0e08Rq2uuelyLbCLWuJWq_axuKSZbZvpYeqWtIAde8FjCiO7RPlEc0nyzWBst8RBxQ-Bekg9UXPhxBRcm0HwA.Q2cBSFOQAC-QKDwmjrQXnVQd3jNOppMl9oZfd2yuKeY'; - dapUtils.dapRefreshEncryptedMembership(esampleConfig, sampleCachedToken.token, onDone) - let request = server.requests[0]; - responseHeader['Akamai-DAP-Token'] = encMembership; - request.respond(200, responseHeader, encMembership); - let rtdObj = dapUtils.dapGetEncryptedRtdObj({'encryptedSegments': encMembership}, 504) - expect(config.getConfig().ortb2.user.data).to.deep.include.members(rtdObj.rtd.ortb2.user.data); - expect(JSON.parse(storage.getDataFromLocalStorage(DAP_ENCRYPTED_MEMBERSHIP)).expires_at).to.equal(1643830630); - }); - - it('test dapRefreshEncryptedMembership error response', function () { - dapUtils.dapRefreshEncryptedMembership(esampleConfig, sampleCachedToken.token, onDone) - let request = server.requests[0]; - request.respond(400, responseHeader, 'error'); - expect(config.getConfig().ortb2).to.be.equal(undefined); - }); - - it('test dapRefreshEncryptedMembership 403 error response', function () { - generateRealTimeData({}, () => {}, emoduleConfig, {}); - dapUtils.dapRefreshEncryptedMembership(esampleConfig, sampleCachedToken.token, onDone) - let request = server.requests[0]; - request.respond(403, responseHeader, 'error'); - let requestTokenize = server.requests[1]; - responseHeader['Akamai-DAP-Token'] = sampleCachedToken.token; - requestTokenize.respond(200, responseHeader, ''); - let requestMembership = server.requests[2]; - requestMembership.respond(403, responseHeader, 'error'); - expect(server.requests.length).to.be.equal(DAP_MAX_RETRY_TOKENIZE + 2); - }); - }); - - describe('dapGetEncryptedMembershipFromLocalStorage test', function () { - it('test dapGetEncryptedMembershipFromLocalStorage function with valid cache', function () { - storage.setDataInLocalStorage(DAP_ENCRYPTED_MEMBERSHIP, JSON.stringify(cachedEncryptedMembership)) - expect(JSON.stringify(dapUtils.dapGetEncryptedMembershipFromLocalStorage())).to.equal(JSON.stringify(encMembership)); - }); - - it('test dapGetEncryptedMembershipFromLocalStorage function with invalid cache', function () { - let expiry = Math.round(Date.now() / 1000.0) - 100; // in seconds - let encMembership = {'expiry': expiry, 'encryptedSegments': cachedEncryptedMembership.encryptedSegments} - storage.setDataInLocalStorage(DAP_ENCRYPTED_MEMBERSHIP, JSON.stringify(encMembership)) - expect(dapUtils.dapGetEncryptedMembershipFromLocalStorage()).to.equal(null); - }); - }); - - describe('Akamai-DAP-SS-ID test', function () { - it('Akamai-DAP-SS-ID present in response header', function () { - let expiry = Math.round(Date.now() / 1000.0) + 300; // in seconds - dapUtils.dapRefreshToken(sampleConfig, false, onDone) - let request = server.requests[0]; - let sampleSSID = 'Test_SSID_Spec'; - responseHeader['Akamai-DAP-Token'] = sampleCachedToken.token; - responseHeader['Akamai-DAP-SS-ID'] = sampleSSID; - request.respond(200, responseHeader, ''); - expect(storage.getDataFromLocalStorage(DAP_SS_ID)).to.be.equal(JSON.stringify(sampleSSID)); - }); - - it('Test if Akamai-DAP-SS-ID is present in request header', function () { - let expiry = Math.round(Date.now() / 1000.0) + 100; // in seconds - storage.setDataInLocalStorage(DAP_SS_ID, JSON.stringify('Test_SSID_Spec')) - dapUtils.dapRefreshToken(sampleConfig, false, onDone) - let request = server.requests[0]; - let ssidHeader = request.requestHeaders['Akamai-DAP-SS-ID']; - responseHeader['Akamai-DAP-Token'] = sampleCachedToken.token; - request.respond(200, responseHeader, ''); - expect(ssidHeader).to.be.equal('Test_SSID_Spec'); - }); - }); - - describe('Test gdpr and usp consent handling', function () { - it('Gdpr applies and gdpr consent string not present', function () { - expect(akamaiDapRtdSubmodule.init(null, sampleGdprConsentConfig)).to.equal(false); - }); - - it('Gdpr applies and gdpr consent string is present', function () { - sampleGdprConsentConfig.gdpr.consentString = 'BOJ/P2HOJ/P2HABABMAAAAAZ+A=='; - expect(akamaiDapRtdSubmodule.init(null, sampleGdprConsentConfig)).to.equal(true); - }); - - it('USP consent present and user have opted out', function () { - expect(akamaiDapRtdSubmodule.init(null, sampleUspConsentConfig)).to.equal(false); - }); - - it('USP consent present and user have not been provided with option to opt out', function () { - expect(akamaiDapRtdSubmodule.init(null, {'usp': '1NYY'})).to.equal(false); - }); + let identity = { + type: 'dap-signature:1.0.0' + }; - it('USP consent present and user have not opted out', function () { - expect(akamaiDapRtdSubmodule.init(null, {'usp': '1YNY'})).to.equal(true); + expect(dapUtils.dapGetMembership(config, 'token')).to.equal(null) + const membership = {cohorts: ['1', '5', '7']} + expect(dapUtils.dapMembershipToRtbSegment(membership, config)).to.not.equal(undefined); }); }); }); From e728f54ba4ce90e0bfe65f1d6cd08b594fbe3314 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Tue, 24 May 2022 18:40:10 +0000 Subject: [PATCH 33/76] Prebid 6.26.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index b6daccf79ca..fd038682e1b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "6.26.0-pre", + "version": "6.26.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 1dd7e1bc914..3a5cc891364 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "6.26.0-pre", + "version": "6.26.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From b01ec406a94e1ecd0a6537b1aca085b9101805bd Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Tue, 24 May 2022 18:40:10 +0000 Subject: [PATCH 34/76] Increment version to 6.27.0-pre --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index fd038682e1b..ca2e5412499 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "6.26.0", + "version": "6.27.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 3a5cc891364..27c874c039a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "6.26.0", + "version": "6.27.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 14ae8d5bc28780d7cd64b4913935326ada58b595 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Tue, 24 May 2022 15:58:21 -0400 Subject: [PATCH 35/76] Update fintezaAnalyticsAdapter_spec.js (#8467) --- test/spec/modules/fintezaAnalyticsAdapter_spec.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/spec/modules/fintezaAnalyticsAdapter_spec.js b/test/spec/modules/fintezaAnalyticsAdapter_spec.js index 407ceb305a2..76f77505105 100644 --- a/test/spec/modules/fintezaAnalyticsAdapter_spec.js +++ b/test/spec/modules/fintezaAnalyticsAdapter_spec.js @@ -51,7 +51,7 @@ describe('finteza analytics adapter', function () { describe('track', () => { describe('bid request', () => { - it('builds and sends data', function () { + it('builds and sends request data', function () { const bidderCode = 'Bidder789'; const pauctionId = '5018eb39-f900-4370-b71e-3bb5b48d324f'; @@ -95,7 +95,7 @@ describe('finteza analytics adapter', function () { }); describe('bid response', () => { - it('builds and sends data', function () { + it('builds and sends response data', function () { const bidderCode = 'Bidder789'; const pauctionId = '5018eb39-f900-4370-b71e-3bb5b48d324f'; @@ -154,7 +154,7 @@ describe('finteza analytics adapter', function () { }); describe('bid won', () => { - it('builds and sends data', function () { + it('builds and sends bid won data', function () { const bidderCode = 'Bidder789'; const pauctionId = '5018eb39-f900-4370-b71e-3bb5b48d324f'; @@ -194,7 +194,7 @@ describe('finteza analytics adapter', function () { }); describe('bid timeout', () => { - it('builds and sends data', function () { + it('builds and sends timeout data', function () { const bidderCode = 'biDDer789'; const pauctionId = '5018eb39-f900-4370-b71e-3bb5b48d324f'; From b97a0ead5c36aecf62f7344858fc1e765e858fc9 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Wed, 25 May 2022 07:27:39 -0700 Subject: [PATCH 36/76] Hadron analytics adapter: fix cross-origin exception on init (#8472) --- modules/hadronAnalyticsAdapter.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/hadronAnalyticsAdapter.js b/modules/hadronAnalyticsAdapter.js index 0bf805fb94e..bc112989426 100644 --- a/modules/hadronAnalyticsAdapter.js +++ b/modules/hadronAnalyticsAdapter.js @@ -4,6 +4,7 @@ import adapterManager from '../src/adapterManager.js'; import * as utils from '../src/utils.js'; import CONSTANTS from '../src/constants.json'; import { getStorageManager } from '../src/storageManager.js'; +import {getRefererInfo} from '../src/refererDetection.js'; /** * hadronAnalyticsAdapter.js - Audigent Hadron Analytics Adapter @@ -35,7 +36,10 @@ var pageView = { timezoneOffset: new Date().getTimezoneOffset(), language: window.navigator.language, vendor: window.navigator.vendor, - pageUrl: window.top.location.href, + pageUrl: (() => { + const ri = getRefererInfo(); + return ri.canonicalUrl || ri.referer; + })(), screenWidth: x, screenHeight: y }; From ce8289107f1a95536cac189d3ebcbbf8f9a6f083 Mon Sep 17 00:00:00 2001 From: Jason Lydon <95770514+ftxmoJason@users.noreply.github.com> Date: Wed, 25 May 2022 14:32:20 -0400 Subject: [PATCH 37/76] Pulling the URL check out from the logic. Whatever the user passes will be used (#8473) Co-authored-by: Jason Lydon --- modules/ftrackIdSystem.js | 7 +++---- test/spec/modules/ftrackIdSystem_spec.js | 10 +--------- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/modules/ftrackIdSystem.js b/modules/ftrackIdSystem.js index 21206109ee0..87b62e98097 100644 --- a/modules/ftrackIdSystem.js +++ b/modules/ftrackIdSystem.js @@ -17,7 +17,6 @@ const VENDOR_ID = null; const LOCAL_STORAGE = 'html5'; const FTRACK_STORAGE_NAME = 'ftrackId'; const FTRACK_PRIVACY_STORAGE_NAME = `${FTRACK_STORAGE_NAME}_privacy`; -const FTRACK_URL = 'https://d9.flashtalking.com/d9core'; const storage = getStorageManager({gvlid: VENDOR_ID, moduleName: MODULE_NAME}); let consentInfo = { @@ -102,7 +101,7 @@ export const ftrackIdSubmodule = { } } - if (config.params && config.params.url && config.params.url === FTRACK_URL) { + if (config.params && config.params.url) { var ftrackScript = document.createElement('script'); ftrackScript.setAttribute('src', config.params.url); window.document.body.appendChild(ftrackScript); @@ -146,8 +145,8 @@ export const ftrackIdSubmodule = { utils.logWarn(LOG_PREFIX + 'config.storage.name recommended to be "' + FTRACK_STORAGE_NAME + '".'); } - if (!config.hasOwnProperty('params') || !config.params.hasOwnProperty('url') || config.params.url !== FTRACK_URL) { - utils.logWarn(LOG_PREFIX + 'config.params.url is required for ftrack to run. Url should be "' + FTRACK_URL + '".'); + if (!config.hasOwnProperty('params') || !config.params.hasOwnProperty('url')) { + utils.logWarn(LOG_PREFIX + 'config.params.url is required for ftrack to run.'); return false; } diff --git a/test/spec/modules/ftrackIdSystem_spec.js b/test/spec/modules/ftrackIdSystem_spec.js index b0843ebb99e..a1c49f321a3 100644 --- a/test/spec/modules/ftrackIdSystem_spec.js +++ b/test/spec/modules/ftrackIdSystem_spec.js @@ -95,15 +95,7 @@ describe('FTRACK ID System', () => { delete configMock1.params.url; ftrackIdSubmodule.isConfigOk(configMock1); - expect(logWarnStub.args[0][0]).to.equal(`FTRACK - config.params.url is required for ftrack to run. Url should be "https://d9.flashtalking.com/d9core".`); - }); - - it(`should be rejected if 'storage.param.url' does not exist or is not 'https://d9.flashtalking.com/d9core'`, () => { - let configMock1 = JSON.parse(JSON.stringify(configMock)); - configMock1.params.url = 'https://d9.NOT.flashtalking.com/d9core'; - - ftrackIdSubmodule.isConfigOk(configMock1); - expect(logWarnStub.args[0][0]).to.equal(`FTRACK - config.params.url is required for ftrack to run. Url should be "https://d9.flashtalking.com/d9core".`); + expect(logWarnStub.args[0][0]).to.equal(`FTRACK - config.params.url is required for ftrack to run.`); }); }); From e10668257b6dea0ac0d201e9bfae67a18d52b644 Mon Sep 17 00:00:00 2001 From: johnwier <49074029+johnwier@users.noreply.github.com> Date: Wed, 25 May 2022 11:36:17 -0700 Subject: [PATCH 38/76] Update conversant adapter to accept position from the AdUnit (#8477) --- modules/conversantBidAdapter.js | 4 +- .../spec/modules/conversantBidAdapter_spec.js | 41 ++++++++++++++++--- 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/modules/conversantBidAdapter.js b/modules/conversantBidAdapter.js index 7ee8b1b7681..a6b631372e0 100644 --- a/modules/conversantBidAdapter.js +++ b/modules/conversantBidAdapter.js @@ -93,7 +93,7 @@ export const spec = { copyOptProperty(format[0].h, video, 'h'); } - copyOptProperty(bid.params.position, video, 'pos'); + copyOptProperty(bid.params.position || videoData.pos, video, 'pos'); copyOptProperty(bid.params.mimes || videoData.mimes, video, 'mimes'); copyOptProperty(bid.params.maxduration || videoData.maxduration, video, 'maxduration'); copyOptProperty(bid.params.protocols || videoData.protocols, video, 'protocols'); @@ -105,7 +105,7 @@ export const spec = { const format = convertSizes(bannerData.sizes || bid.sizes); const banner = {format: format}; - copyOptProperty(bid.params.position, banner, 'pos'); + copyOptProperty(bid.params.position || bannerData.pos, banner, 'pos'); imp.banner = banner; } diff --git a/test/spec/modules/conversantBidAdapter_spec.js b/test/spec/modules/conversantBidAdapter_spec.js index 53169326d3b..ebc9879bb84 100644 --- a/test/spec/modules/conversantBidAdapter_spec.js +++ b/test/spec/modules/conversantBidAdapter_spec.js @@ -72,6 +72,7 @@ describe('Conversant adapter tests', function() { video: { context: 'instream', playerSize: [632, 499], + pos: 3 } }, placementCode: 'pcode003', @@ -108,12 +109,14 @@ describe('Conversant adapter tests', function() { { bidder: 'conversant', params: { - site_id: siteId + site_id: siteId, + position: 2, }, mediaTypes: { video: { context: 'instream', - mimes: ['video/mp4', 'video/x-flv'] + mimes: ['video/mp4', 'video/x-flv'], + pos: 7, } }, placementCode: 'pcode005', @@ -147,6 +150,23 @@ describe('Conversant adapter tests', function() { bidId: 'bid006', bidderRequestId: '117d765b87bed38', auctionId: 'req000' + }, + { + bidder: 'conversant', + params: { + site_id: siteId + }, + mediaTypes: { + banner: { + sizes: [[728, 90], [468, 60]], + pos: 5 + } + }, + placementCode: 'pcode001', + transactionId: 'tx001', + bidId: 'bid007', + bidderRequestId: '117d765b87bed38', + auctionId: 'req000' } ]; @@ -245,7 +265,7 @@ describe('Conversant adapter tests', function() { expect(payload).to.have.property('id', 'req000'); expect(payload).to.have.property('at', 1); expect(payload).to.have.property('imp'); - expect(payload.imp).to.be.an('array').with.lengthOf(7); + expect(payload.imp).to.be.an('array').with.lengthOf(8); expect(payload.imp[0]).to.have.property('id', 'bid000'); expect(payload.imp[0]).to.have.property('secure', 1); @@ -287,7 +307,7 @@ describe('Conversant adapter tests', function() { expect(payload.imp[3]).to.have.property('displaymanagerver').that.matches(versionPattern); expect(payload.imp[3]).to.not.have.property('tagid'); expect(payload.imp[3]).to.have.property('video'); - expect(payload.imp[3].video).to.not.have.property('pos'); + expect(payload.imp[3].video).to.have.property('pos', 3); expect(payload.imp[3].video).to.have.property('w', 632); expect(payload.imp[3].video).to.have.property('h', 499); expect(payload.imp[3].video).to.have.property('mimes'); @@ -325,7 +345,7 @@ describe('Conversant adapter tests', function() { expect(payload.imp[5]).to.have.property('displaymanagerver').that.matches(versionPattern); expect(payload.imp[5]).to.not.have.property('tagid'); expect(payload.imp[5]).to.have.property('video'); - expect(payload.imp[5].video).to.not.have.property('pos'); + expect(payload.imp[5].video).to.have.property('pos', 2); expect(payload.imp[5].video).to.not.have.property('w'); expect(payload.imp[5].video).to.not.have.property('h'); expect(payload.imp[5].video).to.have.property('mimes'); @@ -345,6 +365,17 @@ describe('Conversant adapter tests', function() { expect(payload.imp[6].ext).to.have.property('data'); expect(payload.imp[6].ext.data).to.have.property('pbadslot'); + expect(payload.imp[7]).to.have.property('id', 'bid007'); + expect(payload.imp[7]).to.have.property('secure', 1); + expect(payload.imp[7]).to.have.property('bidfloor', 0); + expect(payload.imp[7]).to.have.property('displaymanager', 'Prebid.js'); + expect(payload.imp[7]).to.have.property('displaymanagerver').that.matches(versionPattern); + expect(payload.imp[7]).to.not.have.property('tagid'); + expect(payload.imp[7]).to.have.property('banner'); + expect(payload.imp[7].banner).to.have.property('pos', 5); + expect(payload.imp[7].banner).to.have.property('format'); + expect(payload.imp[7].banner.format).to.deep.equal([{w: 728, h: 90}, {w: 468, h: 60}]); + expect(payload).to.have.property('site'); expect(payload.site).to.have.property('id', siteId); expect(payload.site).to.have.property('mobile').that.is.oneOf([0, 1]); From 80430aa7691de62967fe4ca459489d747f6e2287 Mon Sep 17 00:00:00 2001 From: rajsidhunovatiq <79534312+rajsidhunovatiq@users.noreply.github.com> Date: Wed, 25 May 2022 19:40:52 +0100 Subject: [PATCH 39/76] NovatiqId User Module - Include IAB Vendor ID (#8479) * Novatiq snowflake userId submodule Novatiq snowflake userId submodule initial release * change request updates added novatiq info /modules/userId/userId.md added novatiq info /modules/userId/eids.md added novatiq eids /modules/userId/eids.js added novatiq module in /modules/.submodules.json removed unnecessary value from getId response * Update novatiqIdSystem_spec.js removed unnecessary srcid value * Update novatiqIdSystem.md Novatiq ID System: updated novatiq snowflake ID description * use the sharedId if available and configured * updated docs * test changes * defensive code not required * Use the prebid storage manager instead of using native functions * doc changes * trailing spaces * Allow configuration of the sync URL and to allow callbacks for specific custom partner integrations * update documentation * attempt to fix firefox test timeout * include the AIB Vendor Id Co-authored-by: novatiq <79258366+novatiq@users.noreply.github.com> --- modules/novatiqIdSystem.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/modules/novatiqIdSystem.js b/modules/novatiqIdSystem.js index ae9cc4c818f..661a14ca0ef 100644 --- a/modules/novatiqIdSystem.js +++ b/modules/novatiqIdSystem.js @@ -18,6 +18,11 @@ export const novatiqIdSubmodule = { * @type {string} */ name: 'novatiq', + /** + * used to specify vendor id + * @type {number} + */ + gvlid: 1119, /** * decode the stored id value for passing to bid requests @@ -202,7 +207,7 @@ export const novatiqIdSubmodule = { let sharedId = null; if (this.useSharedId(configParams)) { let cookieOrStorageID = this.getCookieOrStorageID(configParams); - const storage = getStorageManager({moduleName: 'pubCommonId'}); + const storage = getStorageManager({gvlid: this.gvlid, moduleName: 'pubCommonId'}); // first check local storage if (storage.hasLocalStorage()) { From 8aaf01b6ca69225f0d49b98db3ebae57723c7bc5 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Wed, 25 May 2022 11:43:47 -0700 Subject: [PATCH 40/76] Multiple modules: attempt to reduce test flakiness, improved logging for XHR mock race conditions (#8466) * Datablocks bid adapter: do not send metrics during tests * Log contents of XHR mock on test failure * Disable ajax for analytics adapters during tests * Do not assume that test setup did not fail --- src/AnalyticsAdapter.js | 6 ++++- test/mocks/analyticsStub.js | 13 +++++++++++ test/mocks/xhr.js | 22 +++++++++++++++++++ test/spec/AnalyticsAdapter_spec.js | 4 ++++ .../spec/modules/datablocksBidAdapter_spec.js | 17 +++++++------- test/test_deps.js | 1 + 6 files changed, 54 insertions(+), 9 deletions(-) create mode 100644 test/mocks/analyticsStub.js diff --git a/src/AnalyticsAdapter.js b/src/AnalyticsAdapter.js index 80916e41324..ae891966ee1 100644 --- a/src/AnalyticsAdapter.js +++ b/src/AnalyticsAdapter.js @@ -3,6 +3,10 @@ import { ajax } from './ajax.js'; import { logMessage, _each } from './utils.js'; import * as events from './events.js' +export const _internal = { + ajax +}; + const { EVENTS: { AUCTION_INIT, @@ -61,7 +65,7 @@ export default function AnalyticsAdapter({ url, analyticsType, global, handler } } function _callEndpoint({ eventType, args, callback }) { - ajax(url, callback, JSON.stringify({ eventType, args })); + _internal.ajax(url, callback, JSON.stringify({ eventType, args })); } function _enqueue({ eventType, args }) { diff --git a/test/mocks/analyticsStub.js b/test/mocks/analyticsStub.js new file mode 100644 index 00000000000..1023db882e8 --- /dev/null +++ b/test/mocks/analyticsStub.js @@ -0,0 +1,13 @@ +import {_internal} from '../../src/AnalyticsAdapter.js'; + +before(() => { + // stub out analytics networking to avoid random events polluting the global xhr mock + disableAjaxForAnalytics(); +}) + +export function disableAjaxForAnalytics() { + sinon.stub(_internal, 'ajax').callsFake(() => null); +} +export function enableAjaxForAnalytics() { + _internal.ajax.restore(); +} diff --git a/test/mocks/xhr.js b/test/mocks/xhr.js index 9fb8fe87fa0..424100f870c 100644 --- a/test/mocks/xhr.js +++ b/test/mocks/xhr.js @@ -1,3 +1,4 @@ +import {getUniqueIdentifierStr} from '../../src/utils.js'; export let server = sinon.createFakeServer(); export let xhr = global.XMLHttpRequest; @@ -7,3 +8,24 @@ beforeEach(function() { server = sinon.createFakeServer(); xhr = global.XMLHttpRequest; }); + +const bid = getUniqueIdentifierStr().substring(4); +let fid = 0; + +/* eslint-disable */ +afterEach(function () { + if (this?.currentTest?.state === 'failed') { + const prepend = (() => { + const preamble = `[Failure ${bid}-${fid++}]`; + return (s) => s.split('\n').map(s => `${preamble} ${s}`).join('\n'); + })(); + + + console.log(prepend(`XHR mock state after failure (for test '${this.currentTest.fullTitle()}'): ${server.requests.length} requests`)) + server.requests.forEach((req, i) => { + console.log(prepend(`Request #${i}:`)); + console.log(prepend(JSON.stringify(req, null, 2))); + }) + } +}); +/* eslint-enable */ diff --git a/test/spec/AnalyticsAdapter_spec.js b/test/spec/AnalyticsAdapter_spec.js index d9199f47af9..f5dac22cb58 100644 --- a/test/spec/AnalyticsAdapter_spec.js +++ b/test/spec/AnalyticsAdapter_spec.js @@ -2,6 +2,7 @@ import { expect } from 'chai'; import * as events from 'src/events.js'; import CONSTANTS from 'src/constants.json'; import { server } from 'test/mocks/xhr.js'; +import {disableAjaxForAnalytics, enableAjaxForAnalytics} from '../mocks/analyticsStub.js'; const REQUEST_BIDS = CONSTANTS.EVENTS.REQUEST_BIDS; const BID_REQUESTED = CONSTANTS.EVENTS.BID_REQUESTED; @@ -25,6 +26,9 @@ FEATURE: Analytics Adapters API AND an \`example\` instance of \`AnalyticsAdapter\`\n`, () => { let adapter; + before(enableAjaxForAnalytics); + after(disableAjaxForAnalytics); + beforeEach(function () { adapter = new AnalyticsAdapter(config); }); diff --git a/test/spec/modules/datablocksBidAdapter_spec.js b/test/spec/modules/datablocksBidAdapter_spec.js index ff7b0aad48c..8e203510b10 100644 --- a/test/spec/modules/datablocksBidAdapter_spec.js +++ b/test/spec/modules/datablocksBidAdapter_spec.js @@ -305,6 +305,15 @@ let bid_request = { } describe('DatablocksAdapter', function() { + before(() => { + // stub out queue metric to avoid it polluting the global xhr mock during other tests + sinon.stub(spec, 'queue_metric').callsFake(() => null); + }); + + after(() => { + spec.queue_metric.restore(); + }); + describe('All needed functions are available', function() { it(`isBidRequestValid is present and type function`, function () { expect(spec.isBidRequestValid).to.exist.and.to.be.a('function') @@ -377,14 +386,6 @@ describe('DatablocksAdapter', function() { }); }) - describe('queue / send metrics', function() { - it('Should return true', function() { - expect(spec.queue_metric({type: 'test'})).to.be.true; - expect(spec.queue_metric('string')).to.be.false; - expect(spec.send_metrics()).to.be.true; - }); - }) - describe('get_viewability', function() { it('Should return undefined', function() { expect(spec.get_viewability()).to.equal(undefined); diff --git a/test/test_deps.js b/test/test_deps.js index 8b8c9fd3a0f..77fbad93e1c 100644 --- a/test/test_deps.js +++ b/test/test_deps.js @@ -7,3 +7,4 @@ window.process = { require('test/helpers/prebidGlobal.js'); require('test/mocks/adloaderStub.js'); require('test/mocks/xhr.js'); +require('test/mocks/analyticsStub.js'); From 588244423ec563104453af20deac929be49ee67a Mon Sep 17 00:00:00 2001 From: mmoschovas <63253416+mmoschovas@users.noreply.github.com> Date: Thu, 26 May 2022 07:49:23 -0400 Subject: [PATCH 41/76] Update to move floors logic after placement.sizes are defined (#8476) --- modules/colossussspBidAdapter.js | 34 ++++++++++++++++---------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/modules/colossussspBidAdapter.js b/modules/colossussspBidAdapter.js index fec0d1b6510..8fab37a433f 100644 --- a/modules/colossussspBidAdapter.js +++ b/modules/colossussspBidAdapter.js @@ -107,7 +107,7 @@ export const spec = { for (let i = 0; i < validBidRequests.length; i++) { let bid = validBidRequests[i]; - let traff = bid.params.traffic || BANNER + let traff = bid.params.traffic || BANNER; let placement = { placementId: bid.params.placement_id, groupId: bid.params.group_id, @@ -116,19 +116,7 @@ export const spec = { eids: [], floor: {} }; - if (typeof bid.getFloor === 'function') { - let tmpFloor = {}; - for (let size of placement.sizes) { - tmpFloor = bid.getFloor({ - currency: 'USD', - mediaType: traff, - size: size - }); - if (tmpFloor) { - placement.floor[`${size[0]}x${size[1]}`] = tmpFloor.floor; - } - } - } + if (bid.schain) { placement.schain = bid.schain; } @@ -147,9 +135,7 @@ export const spec = { } if (traff === BANNER) { placement.sizes = bid.mediaTypes[BANNER].sizes - } - - if (traff === VIDEO) { + } else if (traff === VIDEO) { placement.sizes = bid.mediaTypes[VIDEO].playerSize; placement.playerSize = bid.mediaTypes[VIDEO].playerSize; placement.minduration = bid.mediaTypes[VIDEO].minduration; @@ -167,6 +153,20 @@ export const spec = { placement.api = bid.mediaTypes[VIDEO].api; placement.linearity = bid.mediaTypes[VIDEO].linearity; } + if (typeof bid.getFloor === 'function') { + let tmpFloor = {}; + for (let size of placement.sizes) { + tmpFloor = bid.getFloor({ + currency: 'USD', + mediaType: traff, + size: size + }); + if (tmpFloor) { + placement.floor[`${size[0]}x${size[1]}`] = tmpFloor.floor; + } + } + } + placements.push(placement); } return { From 0ab7e82d16572b2a34c48301dc615e4c14c3a921 Mon Sep 17 00:00:00 2001 From: Alexander Clouter Date: Thu, 26 May 2022 13:09:17 +0100 Subject: [PATCH 42/76] Adloox Analytics/RTD: prefer gpid over pbadslot (#8455) * Adloox Analytics: use CSS.escape when possible * Adloox Analytics/RTD: support GPID --- modules/adlooxAnalyticsAdapter.js | 14 +++++++++----- modules/adlooxAnalyticsAdapter.md | 6 +++--- modules/adlooxRtdProvider.js | 8 +++----- modules/adlooxRtdProvider.md | 4 ++-- 4 files changed, 17 insertions(+), 15 deletions(-) diff --git a/modules/adlooxAnalyticsAdapter.js b/modules/adlooxAnalyticsAdapter.js index 1091b87a22d..095fb917597 100644 --- a/modules/adlooxAnalyticsAdapter.js +++ b/modules/adlooxAnalyticsAdapter.js @@ -63,15 +63,15 @@ MACRO['pageurl'] = function(b, c) { const refererInfo = getRefererInfo(); return (refererInfo.canonicalUrl || refererInfo.referer || '').substr(0, 300).split(/[?#]/)[0]; }; -MACRO['pbadslot'] = function(b, c) { +MACRO['gpid'] = function(b, c) { const adUnit = find(auctionManager.getAdUnits(), a => b.adUnitCode === a.code); - return deepAccess(adUnit, 'ortb2Imp.ext.data.pbadslot') || getGptSlotInfoForAdUnitCode(b.adUnitCode).gptSlot || b.adUnitCode; + return deepAccess(adUnit, 'ortb2Imp.ext.gpid') || deepAccess(adUnit, 'ortb2Imp.ext.data.pbadslot') || getGptSlotInfoForAdUnitCode(b.adUnitCode).gptSlot || b.adUnitCode; }; -MACRO['pbAdSlot'] = MACRO['pbadslot']; // legacy +MACRO['pbAdSlot'] = MACRO['pbadslot'] = MACRO['gpid']; // legacy const PARAMS_DEFAULT = { 'id1': function(b) { return b.adUnitCode }, - 'id2': '%%pbadslot%%', + 'id2': '%%gpid%%', 'id3': function(b) { return b.bidder }, 'id4': function(b) { return b.adId }, 'id5': function(b) { return b.dealId }, @@ -138,7 +138,11 @@ analyticsAdapter.enableAnalytics = function(config) { toselector: config.options.toselector || function(bid) { let code = getGptSlotInfoForAdUnitCode(bid.adUnitCode).divId || bid.adUnitCode; // https://mathiasbynens.be/notes/css-escapes - code = code.replace(/^\d/, '\\3$& '); + try { + code = CSS.escape(code); + } catch (_) { + code = code.replace(/^\d/, '\\3$& '); + } return `#${code}` }, client: config.options.client, diff --git a/modules/adlooxAnalyticsAdapter.md b/modules/adlooxAnalyticsAdapter.md index e21261d0b8d..203b118652e 100644 --- a/modules/adlooxAnalyticsAdapter.md +++ b/modules/adlooxAnalyticsAdapter.md @@ -106,7 +106,7 @@ For example, you have a number of reporting breakdown slots available in the for tagid: 0, params: { id1: function(b) { return b.adUnitCode }, // do not change when using the Adloox RTD Provider - id2: '%%pbadslot%%', // do not change when using the Adloox RTD Provider + id2: '%%gpid%%', // do not change when using the Adloox RTD Provider id3: function(b) { return b.bidder }, id4: function(b) { return b.adId }, id5: function(b) { return b.dealId }, @@ -125,9 +125,9 @@ For example, you have a number of reporting breakdown slots available in the for The following macros are available - * `%%pbadslot%%`: [Prebid Ad Slot](https://docs.prebid.org/features/pbAdSlot.html) returns [`AdUnit.code`](https://docs.prebid.org/features/pbAdSlot.html) if set otherwise returns [`AdUnit.code`](https://docs.prebid.org/dev-docs/adunit-reference.html#adunit) + * **`%%gpid%%` (alias `%%pbadslot%%`**): [Prebid Ad Slot](https://docs.prebid.org/features/pbAdSlot.html) returns [`AdUnit.code`](https://docs.prebid.org/features/pbAdSlot.html) if set otherwise returns [`AdUnit.code`](https://docs.prebid.org/dev-docs/adunit-reference.html#adunit) * it is recommended you read the [Prebid Ad Slot section in the Adloox RTD Provider documentation](./adlooxRtdProvider.md#prebid-ad-slot) - * `%%pageurl%%`: [`canonicalUrl`](https://docs.prebid.org/dev-docs/publisher-api-reference/setConfig.html#setConfig-Page-URL) from the [`refererInfo` object](https://docs.prebid.org/dev-docs/bidder-adaptor.html#referrers) otherwise uses `referer` + * **`%%pageurl%%`**: [`canonicalUrl`](https://docs.prebid.org/dev-docs/publisher-api-reference/setConfig.html#setConfig-Page-URL) from the [`refererInfo` object](https://docs.prebid.org/dev-docs/bidder-adaptor.html#referrers) otherwise uses `referer` ### Functions diff --git a/modules/adlooxRtdProvider.js b/modules/adlooxRtdProvider.js index bb8334ec8fe..489fadf91f4 100644 --- a/modules/adlooxRtdProvider.js +++ b/modules/adlooxRtdProvider.js @@ -216,13 +216,11 @@ function init(config, userConsent) { function getBidRequestData(reqBidsConfigObj, callback, config, userConsent) { // gptPreAuction runs *after* RTD so pbadslot may not be populated... (╯°□°)╯ ┻━┻ const adUnits = (reqBidsConfigObj.adUnits || getGlobal().adUnits).map(adUnit => { - let path = deepAccess(adUnit, 'ortb2Imp.ext.data.pbadslot'); - if (!path) path = getGptSlotInfoForAdUnitCode(adUnit.code).gptSlot; return { - path: path, + gpid: deepAccess(adUnit, 'ortb2Imp.ext.gpid') || deepAccess(adUnit, 'ortb2Imp.ext.data.pbadslot') || getGptSlotInfoForAdUnitCode(adUnit.code).gptSlot || adUnit.code, unit: adUnit }; - }).filter(adUnit => !!adUnit.path); + }).filter(adUnit => !!adUnit.gpid); let response = {}; function setSegments() { @@ -315,7 +313,7 @@ function getBidRequestData(reqBidsConfigObj, callback, config, userConsent) { } const atfQueue = []; adUnits.map((adUnit, i) => { - const ref = [ adUnit.path ]; + const ref = [ adUnit.gpid ]; if (!config.params.slotinpath) ref.push(adUnit.unit.code); args.push(['s', ref.join('\t')]); diff --git a/modules/adlooxRtdProvider.md b/modules/adlooxRtdProvider.md index 6c75fbc2d8b..466f8ed1ba2 100644 --- a/modules/adlooxRtdProvider.md +++ b/modules/adlooxRtdProvider.md @@ -81,7 +81,7 @@ To use this, you *must* also integrate the [Adloox Analytics Adapter](./adlooxAn You may optionally pass a subsection `params` in the `params` block to the Adloox RTD Provider, these will be passed through to the segment handler as is and as described by the integration guidelines. -**N.B.** If you pass `params` to the Adloox Analytics Adapter, `id1` (`AdUnit.code`) and `id2` (`%%pbadslot%%`) *must* describe a stable identifier otherwise no usable segments will be served and so they *must not* be changed; if `id1` for your inventory could contain a non-stable random number please consult with us before continuing +**N.B.** If you pass `params` to the Adloox Analytics Adapter, `id1` (`AdUnit.code`) and `id2` (`%%gpid%%`) *must* describe a stable identifier otherwise no usable segments will be served and so they *must not* be changed; if `id1` for your inventory could contain a non-stable random number please consult with us before continuing Though our segment technology is fast (less than 10ms) the time it takes for the users device to connect to our service and fetch the segments may not be. For this reason we recommend setting `auctionDelay` no lower than 100ms and if possible you should explore using user-agent sourced information such as [NetworkInformation.{rtt,downlink,...}](https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation) to dynamically tune this for each user. @@ -94,7 +94,7 @@ You may use one of two ways to do achieve this: * for display inventory [using GPT](https://developers.google.com/publisher-tag/guides/get-started) you may configure Prebid.js to automatically use the [full ad unit path](https://developers.google.com/publisher-tag/reference#googletag.Slot_getAdUnitPath) 1. include the [`gptPreAuction` module](https://docs.prebid.org/dev-docs/modules/gpt-pre-auction.html) 1. wrap both `pbjs.setConfig({...})` and `pbjs.enableAnalytics({...})` with `googletag.cmd.push(function() { ... })` - * set `pbadslot` in the [first party data](https://docs.prebid.org/dev-docs/adunit-reference.html#first-party-data) variable `AdUnit.ortb2Imp.ext.data.pbadslot` for all your ad units + * set `gpid` (or `pbadslot`) in the [first party data](https://docs.prebid.org/dev-docs/adunit-reference.html#first-party-data) variable `AdUnit.ortb2Imp.ext.gpid` (or `AdUnit.ortb2Imp.ext.data.pbadslot`) for all your ad units ## Timeouts From 2f33e7d3b2b0a62c9890f93cb7b8835d360471bc Mon Sep 17 00:00:00 2001 From: Vikas Srivastava <30315503+visrivastava@users.noreply.github.com> Date: Thu, 26 May 2022 17:47:13 +0530 Subject: [PATCH 43/76] Akamai RTD: fixed bugs on rtd module and added the entropy values (#8470) * Fixed bugs on rtd module and added the entropy values required by Akamai DAP * Fixed the timeout issue in build browserstack tests * Fixing review comments * Fixing review comments - using storage manager for managing localStorage * Fixing review comments - using loadExternalScript method to load the script * Fixed unit test case * Fixing review comments - Added consent handling --- .../gpt/akamaidap_segments_example.html | 8 +- modules/.submodules.json | 1 + modules/akamaiDapRtdProvider.js | 590 ++++++++++++++---- modules/akamaiDapRtdProvider.md | 8 +- src/adloader.js | 3 +- .../spec/modules/akamaiDapRtdProvider_spec.js | 510 +++++++++++---- 6 files changed, 875 insertions(+), 245 deletions(-) diff --git a/integrationExamples/gpt/akamaidap_segments_example.html b/integrationExamples/gpt/akamaidap_segments_example.html index e85ac8e1337..b4e7495002e 100644 --- a/integrationExamples/gpt/akamaidap_segments_example.html +++ b/integrationExamples/gpt/akamaidap_segments_example.html @@ -68,6 +68,7 @@ } }, realTimeData: { + auctionDelay: 2000, dataProviders: [ { name: "dap", @@ -76,9 +77,10 @@ apiHostname: "prebid.dap.akadns.net", apiVersion: "x1", domain: "prebid.org", - identityType: "dap-signature:1.0.0", - segtax: 503, - tokenTtl: 5, + identityType: "dap-signature:1.3.0", + segtax: 504, + dapFpUrl: 'https://dap-dist.akamaized.net/dapfingerprinting.js', + dapFpTimeout: 1500 } } ] diff --git a/modules/.submodules.json b/modules/.submodules.json index 9402d0561fa..8d62f30d7c4 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -52,6 +52,7 @@ ], "rtdModule": [ "airgridRtdProvider", + "akamaiDapRtdProvider", "browsiRtdProvider", "dgkeywordRtdProvider", "geoedgeRtdProvider", diff --git a/modules/akamaiDapRtdProvider.js b/modules/akamaiDapRtdProvider.js index aca984d39c8..27d8516f3b9 100644 --- a/modules/akamaiDapRtdProvider.js +++ b/modules/akamaiDapRtdProvider.js @@ -10,12 +10,22 @@ import {config} from '../src/config.js'; import {getStorageManager} from '../src/storageManager.js'; import {submodule} from '../src/hook.js'; import {isPlainObject, mergeDeep, logMessage, logInfo, logError} from '../src/utils.js'; +import { loadExternalScript } from '../src/adloader.js'; const MODULE_NAME = 'realTimeData'; const SUBMODULE_NAME = 'dap'; +const MODULE_CODE = 'akamaidap'; + +export const DAP_TOKEN = 'async_dap_token'; +export const DAP_MEMBERSHIP = 'async_dap_membership'; +export const DAP_ENCRYPTED_MEMBERSHIP = 'encrypted_dap_membership'; +export const DAP_SS_ID = 'dap_ss_id'; +export const DAP_DEFAULT_TOKEN_TTL = 3600; // in seconds +export const DAP_MAX_RETRY_TOKENIZE = 1; +export const DAP_CLIENT_ENTROPY = 'dap_client_entropy' -export const SEGMENTS_STORAGE_KEY = 'akamaiDapSegments'; export const storage = getStorageManager({gvlid: null, moduleName: SUBMODULE_NAME}); +let dapRetryTokenize = 0; /** * Lazy merge objects. @@ -53,60 +63,92 @@ export function addRealTimeData(rtd) { * Real-time data retrieval from Audigent * @param {Object} reqBidsConfigObj * @param {function} onDone - * @param {Object} rtdConfi + * @param {Object} rtdConfig * @param {Object} userConsent */ export function getRealTimeData(bidConfig, onDone, rtdConfig, userConsent) { - logInfo('DEBUG(getRealTimeData) - ENTER'); + let entropyDict = JSON.parse(storage.getDataFromLocalStorage(DAP_CLIENT_ENTROPY)); + let loadScriptPromise = new Promise((resolve, reject) => { + if (rtdConfig && rtdConfig.params && rtdConfig.params.dapFpTimeout && Number.isInteger(rtdConfig.params.dapFpTimeout)) { + setTimeout(reject, rtdConfig.params.dapFpTimeout, Error('DapFP script could not be loaded')); + } + if (entropyDict && entropyDict.expires_at > Math.round(Date.now() / 1000.0)) { + logMessage('Using cached entropy'); + resolve(); + } else { + if (typeof window.dapCalculateEntropy === 'function') { + window.dapCalculateEntropy(resolve, reject); + } else { + if (rtdConfig && rtdConfig.params && dapUtils.isValidHttpsUrl(rtdConfig.params.dapFpUrl)) { + loadExternalScript(rtdConfig.params.dapFpUrl, MODULE_CODE, () => { dapUtils.dapGetEntropy(resolve, reject) }); + } else { + reject(Error('Please check if dapFpUrl is specified and is valid under config.params')); + } + } + } + }); + loadScriptPromise + .catch((error) => { + logError('Entropy could not be calculated due to: ', error.message); + }) + .finally(() => { + generateRealTimeData(bidConfig, onDone, rtdConfig, userConsent); + }); +} + +export function generateRealTimeData(bidConfig, onDone, rtdConfig, userConsent) { + logInfo('DEBUG(generateRealTimeData) - ENTER'); logMessage(' - apiHostname: ' + rtdConfig.params.apiHostname); logMessage(' - apiVersion: ' + rtdConfig.params.apiVersion); - let jsonData = storage.getDataFromLocalStorage(SEGMENTS_STORAGE_KEY); + dapRetryTokenize = 0; + var jsonData = null; + if (rtdConfig && isPlainObject(rtdConfig.params)) { + if (rtdConfig.params.segtax == 504) { + let encMembership = dapUtils.dapGetEncryptedMembershipFromLocalStorage(); + if (encMembership) { + jsonData = dapUtils.dapGetEncryptedRtdObj(encMembership, rtdConfig.params.segtax) + } + } else { + let membership = dapUtils.dapGetMembershipFromLocalStorage(); + if (membership) { + jsonData = dapUtils.dapGetRtdObj(membership, rtdConfig.params.segtax) + } + } + } if (jsonData) { - let data = JSON.parse(jsonData); - if (data.rtd) { - addRealTimeData(data.rtd); + if (jsonData.rtd) { + addRealTimeData(jsonData.rtd); onDone(); - logInfo('DEBUG(getRealTimeData) - 1'); + logInfo('DEBUG(generateRealTimeData) - 1'); // Don't return - ensure the data is always fresh. } } + // Calling setTimeout to release the main thread so that the bid request could be sent. + setTimeout(callDapAPIs, 0, bidConfig, onDone, rtdConfig, userConsent); +} +function callDapAPIs(bidConfig, onDone, rtdConfig, userConsent) { if (rtdConfig && isPlainObject(rtdConfig.params)) { let config = { api_hostname: rtdConfig.params.apiHostname, api_version: rtdConfig.params.apiVersion, domain: rtdConfig.params.domain, - segtax: rtdConfig.params.segtax - }; - let identity = { - type: rtdConfig.params.identityType + segtax: rtdConfig.params.segtax, + identity: {type: rtdConfig.params.identityType} }; - let token = dapUtils.dapGetToken(config, identity, rtdConfig.params.tokenTtl); - if (token !== null) { - let membership = dapUtils.dapGetMembership(config, token); - let udSegment = dapUtils.dapMembershipToRtbSegment(membership, config); - logMessage('DEBUG(getRealTimeData) - token: ' + token + ', user.data.segment: ', udSegment); - let data = { - rtd: { - ortb2: { - user: { - data: [ - udSegment - ] - }, - site: { - ext: { - data: { - dapSAID: membership.said - } - } - } - } - } - }; - storage.setDataInLocalStorage(SEGMENTS_STORAGE_KEY, JSON.stringify(data)); - onDone(); + let refreshMembership = true; + let token = dapUtils.dapGetTokenFromLocalStorage(); + logMessage('token is: ', token); + if (token !== null) { // If token is not null then check the membership in storage and add the RTD object + if (config.segtax == 504) { // Follow the encrypted membership path + dapUtils.dapRefreshEncryptedMembership(config, token, onDone) // Get the encrypted membership from server + refreshMembership = false; + } else { + dapUtils.dapRefreshMembership(config, token, onDone) // Get the membership from server + refreshMembership = false; + } } + dapUtils.dapRefreshToken(config, refreshMembership, onDone) // Refresh Token and membership in all the cases } } @@ -117,6 +159,9 @@ export function getRealTimeData(bidConfig, onDone, rtdConfig, userConsent) { * @return {boolean} */ function init(provider, userConsent) { + if (dapUtils.checkConsent(userConsent) === false) { + return false; + } return true; } @@ -128,104 +173,190 @@ export const akamaiDapRtdSubmodule = { }; submodule(MODULE_NAME, akamaiDapRtdSubmodule); - export const dapUtils = { + dapGetEntropy: function(resolve, reject) { + if (typeof window.dapCalculateEntropy === 'function') { + window.dapCalculateEntropy(resolve, reject); + } else { + reject(Error('window.dapCalculateEntropy function is not defined')) + } + }, - dapGetToken: function(config, identity, ttl) { + dapGetTokenFromLocalStorage: function(ttl) { let now = Math.round(Date.now() / 1000.0); // in seconds - let storageName = 'async_dap_token'; let token = null; - - if (ttl == 0) { - localStorage.removeItem(storageName); + let item = JSON.parse(storage.getDataFromLocalStorage(DAP_TOKEN)); + if (item) { + if (now < item.expires_at) { + token = item.token; + } } + return token; + }, - let item = JSON.parse(localStorage.getItem(storageName)); - if (item == null) { - item = { - expires_at: now - 1, - token: null - }; - } else { - token = item.token; - } - - if (now > item.expires_at) { - dapUtils.dapLog('Token missing or expired, fetching a new one...'); - // Trigger a refresh - let configAsync = {...config}; - dapUtils.dapTokenize(configAsync, identity, - function(token, status, xhr) { - item.expires_at = now + ttl; - item.token = token; - localStorage.setItem(storageName, JSON.stringify(item)); - dapUtils.dapLog('Successfully updated and stored token; expires in ' + ttl + ' seconds'); - let deviceId100 = xhr.getResponseHeader('Akamai-DAP-100'); - if (deviceId100 != null) { - localStorage.setItem('dap_deviceId100', deviceId100); - dapUtils.dapLog('Successfully stored DAP 100 Device ID: ' + deviceId100); + dapRefreshToken: function(config, refreshMembership, onDone) { + dapUtils.dapLog('Token missing or expired, fetching a new one...'); + // Trigger a refresh + let now = Math.round(Date.now() / 1000.0); // in seconds + let item = {} + let configAsync = {...config}; + dapUtils.dapTokenize(configAsync, config.identity, onDone, + function(token, status, xhr, onDone) { + item.expires_at = now + DAP_DEFAULT_TOKEN_TTL; + let exp = dapUtils.dapExtractExpiryFromToken(token) + if (typeof exp == 'number') { + item.expires_at = exp - 10; + } + item.token = token; + storage.setDataInLocalStorage(DAP_TOKEN, JSON.stringify(item)); + dapUtils.dapLog('Successfully updated and stored token; expires at ' + item.expires_at); + let dapSSID = xhr.getResponseHeader('Akamai-DAP-SS-ID'); + if (dapSSID) { + storage.setDataInLocalStorage(DAP_SS_ID, JSON.stringify(dapSSID)); + } + let deviceId100 = xhr.getResponseHeader('Akamai-DAP-100'); + if (deviceId100 != null) { + storage.setDataInLocalStorage('dap_deviceId100', deviceId100); + dapUtils.dapLog('Successfully stored DAP 100 Device ID: ' + deviceId100); + } + if (refreshMembership) { + if (config.segtax == 504) { + dapUtils.dapRefreshEncryptedMembership(config, token, onDone); + } else { + dapUtils.dapRefreshMembership(config, token, onDone); } - }, - function(xhr, status, error) { - logError('ERROR(' + error + '): failed to retrieve token! ' + status); } - ); - } - - return token; + }, + function(xhr, status, error, onDone) { + logError('ERROR(' + error + '): failed to retrieve token! ' + status); + onDone() + } + ); }, - dapGetMembership: function(config, token) { + dapGetMembershipFromLocalStorage: function() { let now = Math.round(Date.now() / 1000.0); // in seconds - let storageName = 'async_dap_membership'; - let maxTtl = 3600; // if the cached membership is older than this, return null let membership = null; - let item = JSON.parse(localStorage.getItem(storageName)); - if (item == null || (now - item.expires_at) > maxTtl) { - item = { - expires_at: now - 1, - said: null, - cohorts: null, - attributes: null - }; - } else { - membership = { - said: item.said, - cohorts: item.cohorts, - attributes: null - }; + let item = JSON.parse(storage.getDataFromLocalStorage(DAP_MEMBERSHIP)); + if (item) { + if (now < item.expires_at) { + membership = { + said: item.said, + cohorts: item.cohorts, + attributes: null + }; + } } + return membership; + }, - // Always refresh the cached membership. + dapRefreshMembership: function(config, token, onDone) { + let now = Math.round(Date.now() / 1000.0); // in seconds + let item = {} let configAsync = {...config}; - dapUtils.dapMembership(configAsync, token, - function(membership, status, xhr) { - item.expires_at = now + maxTtl; + dapUtils.dapMembership(configAsync, token, onDone, + function(membership, status, xhr, onDone) { + item.expires_at = now + DAP_DEFAULT_TOKEN_TTL; + let exp = dapUtils.dapExtractExpiryFromToken(membership.said) + if (typeof exp == 'number') { + item.expires_at = exp - 10; + } item.said = membership.said; item.cohorts = membership.cohorts; - localStorage.setItem(storageName, JSON.stringify(item)); + storage.setDataInLocalStorage(DAP_MEMBERSHIP, JSON.stringify(item)); dapUtils.dapLog('Successfully updated and stored membership:'); dapUtils.dapLog(item); + + let data = dapUtils.dapGetRtdObj(item, config.segtax) + dapUtils.checkAndAddRealtimeData(data, config.segtax); + onDone(); }, - function(xhr, status, error) { + function(xhr, status, error, onDone) { logError('ERROR(' + error + '): failed to retrieve membership! ' + status); + if (status == 403 && dapRetryTokenize < DAP_MAX_RETRY_TOKENIZE) { + dapRetryTokenize++; + dapUtils.dapRefreshToken(config, true, onDone); + } else { + onDone(); + } } ); + }, - return membership; + dapGetEncryptedMembershipFromLocalStorage: function() { + let now = Math.round(Date.now() / 1000.0); // in seconds + let encMembership = null; + let item = JSON.parse(storage.getDataFromLocalStorage(DAP_ENCRYPTED_MEMBERSHIP)); + if (item) { + if (now < item.expires_at) { + encMembership = { + encryptedSegments: item.encryptedSegments + }; + } + } + return encMembership; + }, + + dapRefreshEncryptedMembership: function(config, token, onDone) { + let now = Math.round(Date.now() / 1000.0); // in seconds + let item = {}; + let configAsync = {...config}; + dapUtils.dapEncryptedMembership(configAsync, token, onDone, + function(encToken, status, xhr, onDone) { + item.expires_at = now + DAP_DEFAULT_TOKEN_TTL; + let exp = dapUtils.dapExtractExpiryFromToken(encToken) + if (typeof exp == 'number') { + item.expires_at = exp - 10; + } + item.encryptedSegments = encToken; + storage.setDataInLocalStorage(DAP_ENCRYPTED_MEMBERSHIP, JSON.stringify(item)); + dapUtils.dapLog('Successfully updated and stored encrypted membership:'); + dapUtils.dapLog(item); + + let encData = dapUtils.dapGetEncryptedRtdObj(item, config.segtax); + dapUtils.checkAndAddRealtimeData(encData, config.segtax); + onDone(); + }, + function(xhr, status, error, onDone) { + logError('ERROR(' + error + '): failed to retrieve encrypted membership! ' + status); + if (status == 403 && dapRetryTokenize < DAP_MAX_RETRY_TOKENIZE) { + dapRetryTokenize++; + dapUtils.dapRefreshToken(config, true, onDone); + } else { + onDone(); + } + } + ); + }, + + /** + * DESCRIPTION + * Extract expiry value from a token + */ + dapExtractExpiryFromToken: function(token) { + let exp = null; + if (token) { + const tokenArray = token.split('..'); + if (tokenArray && tokenArray.length > 0) { + let decode = atob(tokenArray[0]) + let header = JSON.parse(decode.replace(/"/g, '"')); + exp = header.exp; + } + } + return exp }, /** * DESCRIPTION * * Convert a DAP membership response to an OpenRTB2 segment object suitable - * for insertion into user.data.segment or site.data.segment. + * for insertion into user.data.segment or site.data.segment and add it to the rtd obj. */ - dapMembershipToRtbSegment: function(membership, config) { + dapGetRtdObj: function(membership, segtax) { let segment = { name: 'dap.akamai.com', ext: { - 'segtax': config.segtax + 'segtax': segtax }, segment: [] }; @@ -234,7 +365,83 @@ export const dapUtils = { segment.segment.push({ id: i }); } } - return segment; + let data = { + rtd: { + ortb2: { + user: { + data: [ + segment + ] + }, + site: { + ext: { + data: { + dapSAID: membership.said + } + } + } + } + } + }; + return data; + }, + + /** + * DESCRIPTION + * + * Convert a DAP membership response to an OpenRTB2 segment object suitable + * for insertion into user.data.segment or site.data.segment and add it to the rtd obj. + */ + dapGetEncryptedRtdObj: function(encToken, segtax) { + let segment = { + name: 'dap.akamai.com', + ext: { + 'segtax': segtax + }, + segment: [] + }; + if (encToken != null) { + segment.segment.push({ id: encToken.encryptedSegments }); + } + let encData = { + rtd: { + ortb2: { + user: { + data: [ + segment + ] + } + } + } + }; + return encData; + }, + + checkAndAddRealtimeData: function(data, segtax) { + if (data.rtd) { + if (segtax == 504 && dapUtils.checkIfSegmentsAlreadyExist(data.rtd, 504)) { + logMessage('DEBUG(handleInit): rtb Object already added'); + } else { + addRealTimeData(data.rtd); + } + logInfo('DEBUG(checkAndAddRealtimeData) - 1'); + } + }, + + checkIfSegmentsAlreadyExist: function(rtd, segtax) { + let segmentsExist = false + let ortb2 = config.getConfig('ortb2') || {}; + if (ortb2.user && ortb2.user.data && ortb2.user.data.length > 0) { + for (let i = 0; i < ortb2.user.data.length; i++) { + let element = ortb2.user.data[i] + if (element.ext && element.ext.segtax == segtax) { + segmentsExist = true + logMessage('DEBUG(checkIfSegmentsAlreadyExist): rtb Object already added: ', ortb2.user.data); + break; + } + } + } + return segmentsExist }, dapLog: function(args) { @@ -248,6 +455,35 @@ export const dapUtils = { logInfo('%cDAP Client', css, args); }, + isValidHttpsUrl: function(urlString) { + let url; + try { + url = new URL(urlString); + } catch (_) { + return false; + } + return url.protocol === 'https:'; + }, + + checkConsent: function(userConsent) { + let consent = true; + + if (userConsent && userConsent.gdpr && userConsent.gdpr.gdprApplies) { + const gdpr = userConsent.gdpr; + const hasGdpr = (gdpr && typeof gdpr.gdprApplies === 'boolean' && gdpr.gdprApplies) ? 1 : 0; + const gdprConsentString = hasGdpr ? gdpr.consentString : ''; + if (hasGdpr && (!gdprConsentString || gdprConsentString === '')) { + logError('akamaiDapRtd submodule requires consent string to call API'); + consent = false; + } + } else if (userConsent && userConsent.usp) { + const usp = userConsent.usp; + consent = usp[1] !== 'N' && usp[2] !== 'Y'; + } + + return consent; + }, + /******************************************************************************* * * V2 (And Beyond) API @@ -293,23 +529,23 @@ export const dapUtils = { * function( response, status, xhr } { token = response; }, * function( xhr, status, error ) { ; } // handle error */ - dapTokenize: function(config, identity, onSuccess = null, onError = null) { + dapTokenize: function(config, identity, onDone, onSuccess = null, onError = null) { if (onError == null) { - onError = function(xhr, status, error) {}; + onError = function(xhr, status, error, onDone) {}; } if (config == null || typeof (config) == typeof (undefined)) { - onError(null, 'Invalid config object', 'ClientError'); + onError(null, 'Invalid config object', 'ClientError', onDone); return; } if (typeof (config.domain) != 'string') { - onError(null, 'Invalid config.domain: must be a string', 'ClientError'); + onError(null, 'Invalid config.domain: must be a string', 'ClientError', onDone); return; } if (config.domain.length <= 0) { - onError(null, 'Invalid config.domain: must have non-zero length', 'ClientError'); + onError(null, 'Invalid config.domain: must have non-zero length', 'ClientError', onDone); return; } @@ -318,22 +554,22 @@ export const dapUtils = { } if (typeof (config.api_version) != 'string') { - onError(null, "Invalid api_version: must be a string like 'x1', etc.", 'ClientError'); + onError(null, "Invalid api_version: must be a string like 'x1', etc.", 'ClientError', onDone); return; } if (!(('api_hostname') in config) || typeof (config.api_hostname) != 'string' || config.api_hostname.length == 0) { - onError(null, 'Invalid api_hostname: must be a non-empty string', 'ClientError'); + onError(null, 'Invalid api_hostname: must be a non-empty string', 'ClientError', onDone); return; } if (identity == null || typeof (identity) == typeof (undefined)) { - onError(null, 'Invalid identity object', 'ClientError'); + onError(null, 'Invalid identity object', 'ClientError', onDone); return; } if (!('type' in identity) || typeof (identity.type) != 'string' || identity.type.length <= 0) { - onError(null, "Identity must contain a valid 'type' field", 'ClientError'); + onError(null, "Identity must contain a valid 'type' field", 'ClientError', onDone); return; } @@ -348,6 +584,11 @@ export const dapUtils = { apiParams.attributes = identity.attributes; } + let entropyDict = JSON.parse(storage.getDataFromLocalStorage(DAP_CLIENT_ENTROPY)); + if (entropyDict && entropyDict.entropy) { + apiParams.entropy = entropyDict.entropy; + } + let method; let body; let path; @@ -359,10 +600,16 @@ export const dapUtils = { body = JSON.stringify(apiParams); break; default: - onError(null, 'Invalid api_version: ' + config.api_version, 'ClientError'); + onError(null, 'Invalid api_version: ' + config.api_version, 'ClientError', onDone); return; } + let customHeaders = {'Content-Type': 'application/json'}; + let dapSSID = JSON.parse(storage.getDataFromLocalStorage(DAP_SS_ID)); + if (dapSSID) { + customHeaders['Akamai-DAP-SS-ID'] = dapSSID; + } + let url = 'https://' + config.api_hostname + path; let cb = { success: (response, request) => { @@ -373,19 +620,16 @@ export const dapUtils = { token = request.getResponseHeader('Akamai-DAP-Token'); break; } - onSuccess(token, request.status, request); + onSuccess(token, request.status, request, onDone); }, error: (request, error) => { - onError(request, request.statusText, error); + onError(request, request.statusText, error, onDone); } }; ajax(url, cb, body, { method: method, - customHeaders: { - 'Content-Type': 'application/json', - 'Pragma': 'akamai-x-cache-on' - } + customHeaders: customHeaders }); }, @@ -411,7 +655,7 @@ export const dapUtils = { * api_hostname: 'api.dap.akadns.net', * }; * - * // token from dap_x1_tokenize + * // token from dap_tokenize * * dapMembership( config, token, * function( membership, status, xhr ) { @@ -422,13 +666,13 @@ export const dapUtils = { * } ); * */ - dapMembership: function(config, token, onSuccess = null, onError = null) { + dapMembership: function(config, token, onDone, onSuccess = null, onError = null) { if (onError == null) { - onError = function(xhr, status, error) {}; + onError = function(xhr, status, error, onDone) {}; } if (config == null || typeof (config) == typeof (undefined)) { - onError(null, 'Invalid config object', 'ClientError'); + onError(null, 'Invalid config object', 'ClientError', onDone); return; } @@ -437,32 +681,32 @@ export const dapUtils = { } if (typeof (config.api_version) != 'string') { - onError(null, "Invalid api_version: must be a string like 'x1', etc.", 'ClientError'); + onError(null, "Invalid api_version: must be a string like 'x1', etc.", 'ClientError', onDone); return; } if (!(('api_hostname') in config) || typeof (config.api_hostname) != 'string' || config.api_hostname.length == 0) { - onError(null, 'Invalid api_hostname: must be a non-empty string', 'ClientError'); + onError(null, 'Invalid api_hostname: must be a non-empty string', 'ClientError', onDone); return; } if (token == null || typeof (token) != 'string') { - onError(null, 'Invalid token: must be a non-null string', 'ClientError'); + onError(null, 'Invalid token: must be a non-null string', 'ClientError', onDone); return; } let path = '/data-activation/' + - config.api_version + - '/token/' + token + - '/membership'; + config.api_version + + '/token/' + token + + '/membership'; let url = 'https://' + config.api_hostname + path; let cb = { success: (response, request) => { - onSuccess(JSON.parse(response), request.status, request); + onSuccess(JSON.parse(response), request.status, request, onDone); }, error: (error, request) => { - onError(request, request.status, error); + onError(request, request.status, error, onDone); } }; @@ -470,5 +714,91 @@ export const dapUtils = { method: 'GET', customHeaders: {} }); + }, + + /** + * SYNOPSIS + * + * dapEncryptedMembership( config, token, onSuccess, onError ); + * + * DESCRIPTION + * + * Return the audience segment membership along with a new Secure Advertising + * ID for this token in encrypted format. + * + * PARAMETERS + * + * config: an array of system configuration parameters + * + * token: the token previously returned from the tokenize API + * + * EXAMPLE + * + * config = { + * api_hostname: 'api.dap.akadns.net', + * }; + * + * // token from dap_tokenize + * + * dapEncryptedMembership( config, token, + * function( membership, status, xhr ) { + * // Run auction with membership.segments and membership.said after decryption + * }, + * function( xhr, status, error ) { + * // error + * } ); + * + */ + dapEncryptedMembership: function(config, token, onDone, onSuccess = null, onError = null) { + if (onError == null) { + onError = function(xhr, status, error, onDone) {}; + } + + if (config == null || typeof (config) == typeof (undefined)) { + onError(null, 'Invalid config object', 'ClientError', onDone); + return; + } + + if (!('api_version' in config) || (typeof (config.api_version) == 'string' && config.api_version.length == 0)) { + config.api_version = 'x1'; + } + + if (typeof (config.api_version) != 'string') { + onError(null, "Invalid api_version: must be a string like 'x1', etc.", 'ClientError', onDone); + return; + } + + if (!(('api_hostname') in config) || typeof (config.api_hostname) != 'string' || config.api_hostname.length == 0) { + onError(null, 'Invalid api_hostname: must be a non-empty string', 'ClientError', onDone); + return; + } + + if (token == null || typeof (token) != 'string') { + onError(null, 'Invalid token: must be a non-null string', 'ClientError', onDone); + return; + } + let path = '/data-activation/' + + config.api_version + + '/token/' + token + + '/membership/encrypt'; + + let url = 'https://' + config.api_hostname + path; + + let cb = { + success: (response, request) => { + let encToken = request.getResponseHeader('Akamai-DAP-Token'); + onSuccess(encToken, request.status, request, onDone); + }, + error: (error, request) => { + onError(request, request.status, error, onDone); + } + }; + ajax(url, cb, undefined, { + method: 'GET', + customHeaders: { + 'Content-Type': 'application/json', + 'Pragma': 'akamai-x-get-extracted-values' + } + }); } } diff --git a/modules/akamaiDapRtdProvider.md b/modules/akamaiDapRtdProvider.md index ade11b88602..5e3b93cc5fc 100644 --- a/modules/akamaiDapRtdProvider.md +++ b/modules/akamaiDapRtdProvider.md @@ -17,6 +17,7 @@ ``` pbjs.setConfig({ realTimeData: { + auctionDelay: 2000, dataProviders: [ { name: "dap", @@ -25,9 +26,10 @@ apiHostname: '', apiVersion: "x1", domain: 'your-domain.com', - identityType: 'email' | 'mobile' | ... | 'dap-signature:1.0.0', - segtax: , - tokenTtl: 5, + identityType: 'email' | 'mobile' | ... | 'dap-signature:1.3.0', + segtax: 504, + dapFpUrl: 'https://dap-dist.akamaized.net/dapfingerprinting.js', + dapFpTimeout: 1500 // Maximum time for dapFP to run } } ] diff --git a/src/adloader.js b/src/adloader.js index db128c6d7ba..c73f83f300c 100644 --- a/src/adloader.js +++ b/src/adloader.js @@ -10,7 +10,8 @@ const _approvedLoadExternalJSList = [ 'adagio', 'browsi', 'brandmetrics', - 'justtag' + 'justtag', + 'akamaidap' ] /** diff --git a/test/spec/modules/akamaiDapRtdProvider_spec.js b/test/spec/modules/akamaiDapRtdProvider_spec.js index b350c2bb529..25688abd99e 100644 --- a/test/spec/modules/akamaiDapRtdProvider_spec.js +++ b/test/spec/modules/akamaiDapRtdProvider_spec.js @@ -1,13 +1,14 @@ import {config} from 'src/config.js'; -import {SEGMENTS_STORAGE_KEY, TOKEN_STORAGE_KEY, dapUtils, addRealTimeData, getRealTimeData, akamaiDapRtdSubmodule, storage} from 'modules/akamaiDapRtdProvider.js'; +import { + dapUtils, + generateRealTimeData, + akamaiDapRtdSubmodule, + storage, DAP_MAX_RETRY_TOKENIZE, DAP_SS_ID, DAP_TOKEN, DAP_MEMBERSHIP, DAP_ENCRYPTED_MEMBERSHIP, +} from 'modules/akamaiDapRtdProvider.js'; import {server} from 'test/mocks/xhr.js'; -import logMessage from 'src/utils.js' const responseHeader = {'Content-Type': 'application/json'}; describe('akamaiDapRtdProvider', function() { - let getDataFromLocalStorageStub; - let getDapTokenStub; - const testReqBidsConfigObj = { adUnits: [ { @@ -18,7 +19,21 @@ describe('akamaiDapRtdProvider', function() { const onDone = function() { return true }; - const onSuccess = function() { return ('request', 200, 'success') }; + const sampleGdprConsentConfig = { + 'gdpr': { + 'consentString': null, + 'vendorData': {}, + 'gdprApplies': true + } + }; + + const sampleUspConsentConfig = { + 'usp': '1YYY' + }; + + const sampleIdentity = { + type: 'dap-signature:1.0.0' + }; const cmoduleConfig = { 'name': 'dap', @@ -28,8 +43,19 @@ describe('akamaiDapRtdProvider', function() { 'apiVersion': 'x1', 'domain': 'prebid.org', 'identityType': 'dap-signature:1.0.0', - 'segtax': 503, - 'tokenTtl': 5 + 'segtax': 503 + } + } + + const emoduleConfig = { + 'name': 'dap', + 'waitForIt': true, + 'params': { + 'apiHostname': 'prebid.dap.akadns.net', + 'apiVersion': 'x1', + 'domain': 'prebid.org', + 'identityType': 'dap-signature:1.0.0', + 'segtax': 504 } } @@ -37,81 +63,119 @@ describe('akamaiDapRtdProvider', function() { 'api_hostname': 'prebid.dap.akadns.net', 'api_version': 'x1', 'domain': 'prebid.org', - 'segtax': 503 + 'segtax': 503, + 'identity': sampleIdentity } - const sampleIdentity = { - type: 'dap-signature:1.0.0' + + const esampleConfig = { + 'api_hostname': 'prebid.dap.akadns.net', + 'api_version': 'x1', + 'domain': 'prebid.org', + 'segtax': 504, + 'identity': sampleIdentity + } + let cacheExpiry = Math.round(Date.now() / 1000.0) + 300; // in seconds + const sampleCachedToken = {'expires_at': cacheExpiry, 'token': 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIn0..6buzBd2BjtgoyaNbHN8YnQ.l38avCfm3sYNy798-ETYOugz0cOx1cCkjACkAhYszxzrZ0sUJ0AiF-NdDXVTiTyp2Ih3vCWKzS0rKJ8lbS1zhyEVWVu91QwtwseM2fBbwA5ggAgBEo5wV-IXqDLPxVnxsPF0D3hP6cNCiH9Q2c-vULfsLhMhG5zvvZDPBbn4hUY5fKB8LoCBTF9rbuuWGYK1nramnb4AlS5UK82wBsHQea1Ou_Kp5wWCMNZ6TZk5qKIuRBfPIAhQblWvHECaHXkg1wyoM9VASs_yNhne7RR-qkwzbFiPFiMJibNOt9hF3_vPDJO5-06ZBjRTP1BllYGWxI-uQX6InzN18Wtun2WHqg.63sH0SNlIRcsK57v0pMujfB_nhU8Y5CuQbsHqH5MGoM'}; + const cachedEncryptedMembership = {'expires_at': cacheExpiry, 'encryptedSegments': 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoic29tZXNlY3JldGludmF1bHQifQ..IvnIUQDqWBVYIS0gbcE9bw.Z4NZGvtogWaWlGH4e-GdYKe_PUc15M2x3Bj85rMWsN1A17mIxQIMOfg2hsQ2tgieLu5LggWPmsFu1Wbph6P0k3kOu1dVReoIhOHzxw50rP0DLHKaEZ5mLMJ7Lcosvwh4miIfFuCHlsX7J0sFgOTAp0zGo1S_UsHLtev1JflhjoSB0AoX95ALbAnyctirPuLJM8gZ1vXTiZ01jpvucGyR1lM4cWjPOeD8jPtgwaPGgSRZXE-3X2Cqy7z4Giam5Uqu74LPWTBuKtUQTGyAXA5QJoP7xwTbsU4O1f69lu3fWNqC92GijeTH1A4Zd_C-WXxWuQlDEURjlkWQoaqTHka2OqlnwukEQIf_v0r5KQQX64CTLhEUH91jeD0-E9ClcIP7pwOLxxqiKoaBmx8Mrnm_6Agj5DtTA1rusy3AL63sI_rsUxrmLrVt0Wft4aCfRkW8QpQxu8clFdOmce0NNCGeBCyCPVw9d9izrILlXJ6rItU2cpFrcbz8uw2otamF5eOFCOY3IzHedWVNNuKHFIUVC_xYSlsYvQ8f2QIP1eiMbmukcuPzmTzjw1h1_7IKaj-jJkXrnrY-TdDgX_4-_Z3rmbpXK2yTR7dBrsg-ubqFbgbKic1b4zlQEO_LbBlgPl3DYdWEuJ8CY2NUt1GfpATQGsufS2FTY1YGw_gkPe3q04l_cgLafDoxHvHh_t_0ZgPjciW82gThB_kN4RP7Mc3krVcXl_P6N1VbV07xyx0hCyVsrrxbLslI8q9wYDiLGci7mNmByM5j7SXV9jPwwPkHtn0HfMJlw2PFbIDPjgG3h7sOyLcBIJTTvuUIgpHPIkRWLIl_4FlIucXbJ7orW2nt5BWleBVHgumzGcnl9ZNcZb3W-dsdYPSOmuj0CY28MRTP2oJ1rzLInbDDpIRffJBtR7SS4nYyy7Vi09PtBigod5YNz1Q0WDSJxr8zeH_aKFaXInw7Bfo_U0IAcLiRgcT0ogsMLeQRjRFy27mr4XNJv3NtHhbdjDAwF2aClCktXyXbQaVdsPH2W71v6m2Q9rB5GQWOktw2s5f-4N1-_EBPGq6TgjF-aJZP22MJVwp1pimT50DfOzoeEqDwi862NNwNNoHmcObH0ZfwAXlhRxsgupNBe20-MNNABj2Phlfv4DUrtQbMdfCnNiypzNCmoTb7G7c_o5_JUwoV_GVkwUtvmi_IUm05P4GeMASSUw8zDKVRAj9h31C2cabM8RjMHGhkbCWpUP2pcz9zlJ7Y76Dh3RLnctfTw7DG9U4w4UlaxNZOgLUiSrGwfyapuSiuGUpuOJkBBLiHmEqAGI5C8oJpcVRccNlHxJAYowgXyFopD5Fr-FkXmv8KMkS0h5C9F6KihmDt5sqDD0qnjM0hHJgq01l7wjVnhEmPpyD-6auFQ-xDnbh1uBOJ_0gCVbRad--FSa5p-dXenggegRxOvZXJ0iAtM6Fal5Og-RCjexIHa9WhVbXhQBJpkSTWwAajZJ64eQ.yih49XB51wE-Xob7COT9OYqBrzBmIMVCQbLFx2UdzkI'}; + const cachedMembership = {'expires_at': cacheExpiry, 'said': 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIn0..QwvU5h0NVJYaJbs5EqWCKA.XNaJHSlnsH8P-yBIr3gIEqavLONWDIFyj7QCHFwJVkwXH_EYkxrk0_26b0uMPzfJp5URnqxKZusMH9DzEJsmj8EMrKQv1y3IYYMsW5_0BdP5bcAWfG6fzOqtMOwLiYRkYiQOqn1ZVGzhovheHWEmNr2_oCY0LvAr3iN1eG_K-l-bBKvBWnwvuuGKquUfCqO8NMMq6wtkecEXM9blqFRZ7oNYmW2aIG7qcHUsrUW7HMr9Ev2Ik0sIeEUsOYrgf_X_VA64RgKSTRugS9FupMv1p54JkHokwduF9pOFmW8QLQi8itFogKGbbgvOTNnmahxQUX5FcrjjYLqHwKqC8htLdlHnO5LWU9l4A7vLXrRurvoSnh0cAJy0GsdoyEwTqR9bwVFHoPquxlJjQ4buEd7PIxpBj9Qg9oOPH3b2upbMTu5CQ9oj526eXPhP5G54nwGklm2AZ3Vggd7jCQJn45Jjiq0iIfsXAtpqS2BssCLBN8WhmUTnStK8m5sux6WUBdrpDESQjPj-EEHVS-DB5rA7icRUh6EzRxzen2rndvHvnwVhSG_l6cwPYuJ0HE0KBmYHOoqNpKwzoGiKFHrf4ReA06iWB3V2TEGJucGujhtQ9_18WwHCeJ1XtQiiO1eqa3tp5MwAbFXawVFl3FFOBgadrPyvGmkmUJ6FCLU2MSwHiYZmANMnJsokFX_6DwoAgO3U_QnvEHIVSvefc7ReeJ8fBDdmrH3LtuLrUpXsvLvEIMQdWQ_SXhjKIi7tOODR8CfrhUcdIjsp3PZs1DpuOcDB6YJKbGnKZTluLUJi3TyHgyi-DHXdTm-jSE5i_DYJGW-t2Gf23FoQhexv4q7gdrfsKfcRJNrZLp6Gd6jl4zHhUtY.nprKBsy9taQBk6dCPbA7BFF0CiGhQOEF_MazZ2bedqk', 'cohorts': ['9', '11', '13']}; + const rtdUserObj = { + name: 'www.dataprovider3.com', + ext: { + taxonomyname: 'iab_audience_taxonomy' + }, + segment: [ + { + id: '1918' + }, + { + id: '1939' + } + ] + }; + + const encRtdUserObj = { + name: 'www.dataprovider3.com', + ext: { + segtax: 504, + taxonomyname: 'iab_audience_taxonomy' + }, + segment: [] + }; + + const cachedRtd = { + rtd: { + ortb2: { + user: { + data: [rtdUserObj] + } + } + } + }; + + let membership = { + said: cachedMembership.said, + cohorts: cachedMembership.cohorts, + attributes: null + }; + let encMembership = { + encryptedSegments: cachedEncryptedMembership.encryptedSegments + }; + encRtdUserObj.segment.push({ id: encMembership.encryptedSegments }); + const cachedEncRtd = { + rtd: { + ortb2: { + user: { + data: [encRtdUserObj] + } + } + } }; beforeEach(function() { config.resetConfig(); - getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage') + storage.removeDataFromLocalStorage(DAP_TOKEN); + storage.removeDataFromLocalStorage(DAP_MEMBERSHIP); + storage.removeDataFromLocalStorage(DAP_ENCRYPTED_MEMBERSHIP); + storage.removeDataFromLocalStorage(DAP_SS_ID); }); afterEach(function () { - getDataFromLocalStorageStub.restore(); }); describe('akamaiDapRtdSubmodule', function() { it('successfully instantiates', function () { - expect(akamaiDapRtdSubmodule.init()).to.equal(true); + expect(akamaiDapRtdSubmodule.init()).to.equal(true); }); }); describe('Get Real-Time Data', function() { it('gets rtd from local storage cache', function() { - const rtdConfig = { - params: { - segmentCache: true - } - }; - const bidConfig = {}; - - const rtdUserObj1 = { - name: 'www.dataprovider3.com', - ext: { - taxonomyname: 'iab_audience_taxonomy' - }, - segment: [ - { - id: '1918' - }, - { - id: '1939' - } - ] - }; - - const cachedRtd = { - rtd: { - ortb2: { - user: { - data: [rtdUserObj1] - } - } - } - }; - - getDataFromLocalStorageStub.withArgs(SEGMENTS_STORAGE_KEY).returns(JSON.stringify(cachedRtd)); + storage.setDataInLocalStorage(DAP_TOKEN, JSON.stringify(sampleCachedToken)); + let dapGetMembershipFromLocalStorageStub = sinon.stub(dapUtils, 'dapGetMembershipFromLocalStorage').returns(membership) + let dapGetRtdObjStub = sinon.stub(dapUtils, 'dapGetRtdObj').returns(cachedRtd) + let dapGetEncryptedMembershipFromLocalStorageStub = sinon.stub(dapUtils, 'dapGetEncryptedMembershipFromLocalStorage').returns(encMembership) + let dapGetEncryptedRtdObjStub = sinon.stub(dapUtils, 'dapGetEncryptedRtdObj').returns(cachedEncRtd) expect(config.getConfig().ortb2).to.be.undefined; - getRealTimeData(bidConfig, () => {}, rtdConfig, {}); - expect(config.getConfig().ortb2.user.data).to.deep.include.members([rtdUserObj1]); - }); - - it('should initalise and return with config', function () { - expect(getRealTimeData(testReqBidsConfigObj, onDone, cmoduleConfig)).to.equal(undefined) + generateRealTimeData(bidConfig, () => {}, emoduleConfig, {}); + expect(config.getConfig().ortb2.user.data).to.deep.include.members([encRtdUserObj]); + generateRealTimeData(bidConfig, () => {}, cmoduleConfig, {}); + expect(config.getConfig().ortb2.user.data).to.deep.include.members([rtdUserObj]); + dapGetRtdObjStub.restore() + dapGetMembershipFromLocalStorageStub.restore() + dapGetEncryptedRtdObjStub.restore() + dapGetEncryptedMembershipFromLocalStorageStub.restore() }); }); describe('dapTokenize', function () { it('dapTokenize error callback', function () { let configAsync = JSON.parse(JSON.stringify(sampleConfig)); - let submoduleCallback = dapUtils.dapTokenize(configAsync, sampleIdentity, - function(token, status, xhr) { + let submoduleCallback = dapUtils.dapTokenize(configAsync, sampleIdentity, onDone, + function(token, status, xhr, onDone) { }, - function(xhr, status, error) { + function(xhr, status, error, onDone) { } ); let request = server.requests[0]; @@ -121,10 +185,10 @@ describe('akamaiDapRtdProvider', function() { it('dapTokenize success callback', function () { let configAsync = JSON.parse(JSON.stringify(sampleConfig)); - let submoduleCallback = dapUtils.dapTokenize(configAsync, sampleIdentity, - function(token, status, xhr) { + let submoduleCallback = dapUtils.dapTokenize(configAsync, sampleIdentity, onDone, + function(token, status, xhr, onDone) { }, - function(xhr, status, error) { + function(xhr, status, error, onDone) { } ); let request = server.requests[0]; @@ -135,40 +199,54 @@ describe('akamaiDapRtdProvider', function() { describe('dapTokenize and dapMembership incorrect params', function () { it('Onerror and config are null', function () { - expect(dapUtils.dapTokenize(null, 'identity', null, null)).to.be.equal(undefined); - expect(dapUtils.dapMembership(null, 'identity', null, null)).to.be.equal(undefined); + expect(dapUtils.dapTokenize(null, 'identity', onDone, null, null)).to.be.equal(undefined); + expect(dapUtils.dapMembership(null, 'identity', onDone, null, null)).to.be.equal(undefined); + expect(dapUtils.dapEncryptedMembership(null, 'identity', onDone, null, null)).to.be.equal(undefined); const config = { 'api_hostname': 'prebid.dap.akadns.net', 'api_version': 1, 'domain': '', 'segtax': 503 }; + const encConfig = { + 'api_hostname': 'prebid.dap.akadns.net', + 'api_version': 1, + 'domain': '', + 'segtax': 504 + }; let identity = { type: 'dap-signature:1.0.0' }; - expect(dapUtils.dapTokenize(config, identity, null, null)).to.be.equal(undefined); - expect(dapUtils.dapMembership(config, 'token', null, null)).to.be.equal(undefined); + expect(dapUtils.dapTokenize(config, identity, onDone, null, null)).to.be.equal(undefined); + expect(dapUtils.dapMembership(config, 'token', onDone, null, null)).to.be.equal(undefined); + expect(dapUtils.dapEncryptedMembership(encConfig, 'token', onDone, null, null)).to.be.equal(undefined); }); + }); - it('dapGetToken success', function () { - let dapTokenizeStub = sinon.stub(dapUtils, 'dapTokenize').returns(onSuccess); - expect(dapUtils.dapGetToken(sampleConfig, 'token', - function(token, status, xhr) { - }, - function(xhr, status, error) { - } - )).to.be.equal(null); - dapTokenizeStub.restore(); + describe('Getting dapTokenize, dapMembership and dapEncryptedMembership from localstorage', function () { + it('dapGetTokenFromLocalStorage success', function () { + storage.setDataInLocalStorage(DAP_TOKEN, JSON.stringify(sampleCachedToken)); + expect(dapUtils.dapGetTokenFromLocalStorage(60)).to.be.equal(sampleCachedToken.token); + }); + + it('dapGetMembershipFromLocalStorage success', function () { + storage.setDataInLocalStorage(DAP_MEMBERSHIP, JSON.stringify(cachedMembership)); + expect(JSON.stringify(dapUtils.dapGetMembershipFromLocalStorage())).to.be.equal(JSON.stringify(membership)); + }); + + it('dapGetEncryptedMembershipFromLocalStorage success', function () { + storage.setDataInLocalStorage(DAP_ENCRYPTED_MEMBERSHIP, JSON.stringify(cachedEncryptedMembership)); + expect(JSON.stringify(dapUtils.dapGetEncryptedMembershipFromLocalStorage())).to.be.equal(JSON.stringify(encMembership)); }); }); describe('dapMembership', function () { it('dapMembership success callback', function () { let configAsync = JSON.parse(JSON.stringify(sampleConfig)); - let submoduleCallback = dapUtils.dapMembership(configAsync, 'token', - function(token, status, xhr) { + let submoduleCallback = dapUtils.dapMembership(configAsync, 'token', onDone, + function(token, status, xhr, onDone) { }, - function(xhr, status, error) { + function(xhr, status, error, onDone) { } ); let request = server.requests[0]; @@ -178,10 +256,38 @@ describe('akamaiDapRtdProvider', function() { it('dapMembership error callback', function () { let configAsync = JSON.parse(JSON.stringify(sampleConfig)); - let submoduleCallback = dapUtils.dapMembership(configAsync, 'token', - function(token, status, xhr) { + let submoduleCallback = dapUtils.dapMembership(configAsync, 'token', onDone, + function(token, status, xhr, onDone) { + }, + function(xhr, status, error, onDone) { + } + ); + let request = server.requests[0]; + request.respond(400, responseHeader, JSON.stringify('error')); + expect(submoduleCallback).to.equal(undefined); + }); + }); + + describe('dapEncMembership', function () { + it('dapEncMembership success callback', function () { + let configAsync = JSON.parse(JSON.stringify(esampleConfig)); + let submoduleCallback = dapUtils.dapEncryptedMembership(configAsync, 'token', onDone, + function(token, status, xhr, onDone) { + }, + function(xhr, status, error, onDone) { + } + ); + let request = server.requests[0]; + request.respond(200, responseHeader, JSON.stringify('success')); + expect(submoduleCallback).to.equal(undefined); + }); + + it('dapEncMembership error callback', function () { + let configAsync = JSON.parse(JSON.stringify(esampleConfig)); + let submoduleCallback = dapUtils.dapEncryptedMembership(configAsync, 'token', onDone, + function(token, status, xhr, onDone) { }, - function(xhr, status, error) { + function(xhr, status, error, onDone) { } ); let request = server.requests[0]; @@ -192,55 +298,243 @@ describe('akamaiDapRtdProvider', function() { describe('dapMembership', function () { it('should invoke the getDapToken and getDapMembership', function () { - let config = { - api_hostname: cmoduleConfig.params.apiHostname, - api_version: cmoduleConfig.params.apiVersion, - domain: cmoduleConfig.params.domain, - segtax: cmoduleConfig.params.segtax - }; - let identity = { - type: cmoduleConfig.params.identityType - }; - let membership = { said: 'item.said1', cohorts: 'item.cohorts', attributes: null }; - let getDapTokenStub = sinon.stub(dapUtils, 'dapGetToken').returns('token3'); - let getDapMembershipStub = sinon.stub(dapUtils, 'dapGetMembership').returns(membership); - let dapTokenizeStub = sinon.stub(dapUtils, 'dapTokenize').returns('response', 200, 'request'); - getRealTimeData(testReqBidsConfigObj, onDone, cmoduleConfig); - expect(getDapTokenStub.calledOnce).to.be.equal(true); + let getDapMembershipStub = sinon.stub(dapUtils, 'dapGetMembershipFromLocalStorage').returns(membership); + generateRealTimeData(testReqBidsConfigObj, onDone, cmoduleConfig); expect(getDapMembershipStub.calledOnce).to.be.equal(true); - getDapTokenStub.restore(); getDapMembershipStub.restore(); - dapTokenizeStub.restore(); }); }); - describe('dapMembershipToRtbSegment', function () { - it('dapMembershipToRtbSegment', function () { - let membership1 = { - said: 'item.said1', - cohorts: 'item.cohorts', - attributes: null + describe('dapEncMembership test', function () { + it('should invoke the getDapToken and getEncDapMembership', function () { + let encMembership = { + encryptedSegments: 'enc.seg', }; + + let getDapEncMembershipStub = sinon.stub(dapUtils, 'dapGetEncryptedMembershipFromLocalStorage').returns(encMembership); + generateRealTimeData(testReqBidsConfigObj, onDone, emoduleConfig); + expect(getDapEncMembershipStub.calledOnce).to.be.equal(true); + getDapEncMembershipStub.restore(); + }); + }); + + describe('dapGetRtdObj test', function () { + it('dapGetRtdObj', function () { const config = { apiHostname: 'prebid.dap.akadns.net', apiVersion: 'x1', domain: 'prebid.org', - tokenTtl: 5, segtax: 503 }; - let identity = { - type: 'dap-signature:1.0.0' - }; - - expect(dapUtils.dapGetMembership(config, 'token')).to.equal(null) + expect(dapUtils.dapRefreshMembership(config, 'token', onDone)).to.equal(undefined) const membership = {cohorts: ['1', '5', '7']} - expect(dapUtils.dapMembershipToRtbSegment(membership, config)).to.not.equal(undefined); + expect(dapUtils.dapGetRtdObj(membership, config.segtax)).to.not.equal(undefined); + }); + }); + + describe('checkAndAddRealtimeData test', function () { + it('add realtime data for segtax 503 and 504', function () { + dapUtils.checkAndAddRealtimeData(cachedEncRtd, 504); + dapUtils.checkAndAddRealtimeData(cachedEncRtd, 504); + expect(config.getConfig().ortb2.user.data).to.deep.include.members([encRtdUserObj]); + dapUtils.checkAndAddRealtimeData(cachedRtd, 503); + expect(config.getConfig().ortb2.user.data).to.deep.include.members([rtdUserObj]); + }); + }); + + describe('dapExtractExpiryFromToken test', function () { + it('test dapExtractExpiryFromToken function', function () { + let tokenWithoutExpiry = 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIn0..6buzBd2BjtgoyaNbHN8YnQ.l38avCfm3sYNy798-ETYOugz0cOx1cCkjACkAhYszxzrZ0sUJ0AiF-NdDXVTiTyp2Ih3vCWKzS0rKJ8lbS1zhyEVWVu91QwtwseM2fBbwA5ggAgBEo5wV-IXqDLPxVnxsPF0D3hP6cNCiH9Q2c-vULfsLhMhG5zvvZDPBbn4hUY5fKB8LoCBTF9rbuuWGYK1nramnb4AlS5UK82wBsHQea1Ou_Kp5wWCMNZ6TZk5qKIuRBfPIAhQblWvHECaHXkg1wyoM9VASs_yNhne7RR-qkwzbFiPFiMJibNOt9hF3_vPDJO5-06ZBjRTP1BllYGWxI-uQX6InzN18Wtun2WHqg.63sH0SNlIRcsK57v0pMujfB_nhU8Y5CuQbsHqH5MGoM' + expect(dapUtils.dapExtractExpiryFromToken(tokenWithoutExpiry)).to.equal(undefined); + let tokenWithExpiry = 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIiwiZXhwIjoxNjQzODMwMzY5fQ..hTbcSQgmmO0HUJJrQ5fRHw.7zjrQXNNVkb-GD0ZhIVhEPcWbyaDBilHTWv-bp1lFZ9mdkSC0QbcAvUbYteiTD7ya23GUwcL2WOW8WgRSHaWHOJe0B5NDqfdUGTzElWfu7fFodRxRgGmwG8Rq5xxteFKLLGHLf1mFYRJKDtjtgajGNUKIDfn9AEt-c5Qz4KU8VolG_KzrLROx-f6Z7MnoPTcwRCj0WjXD6j2D6RAZ80-mKTNIsMIELdj6xiabHcjDJ1WzwtwCZSE2y2nMs451pSYp8W-bFPfZmDDwrkjN4s9ASLlIXcXgxK-H0GsiEbckQOZ49zsIKyFtasBvZW8339rrXi1js-aBh99M7aS5w9DmXPpUDmppSPpwkeTfKiqF0cQiAUq8tpeEQrGDJuw3Qt2.XI8h9Xw-VZj_NOmKtV19wLM63S4snos7rzkoHf9FXCw' + expect(dapUtils.dapExtractExpiryFromToken(tokenWithExpiry)).to.equal(1643830369); + }); + }); + + describe('dapRefreshToken test', function () { + it('test dapRefreshToken success response', function () { + dapUtils.dapRefreshToken(sampleConfig, true, onDone) + let request = server.requests[0]; + responseHeader['Akamai-DAP-Token'] = sampleCachedToken.token; + request.respond(200, responseHeader, JSON.stringify(sampleCachedToken.token)); + expect(JSON.parse(storage.getDataFromLocalStorage(DAP_TOKEN)).token).to.be.equal(sampleCachedToken.token); + }); + + it('test dapRefreshToken success response with deviceid 100', function () { + dapUtils.dapRefreshToken(esampleConfig, true, onDone) + let request = server.requests[0]; + responseHeader['Akamai-DAP-100'] = sampleCachedToken.token; + request.respond(200, responseHeader, ''); + expect(storage.getDataFromLocalStorage('dap_deviceId100')).to.be.equal(sampleCachedToken.token); + }); + + it('test dapRefreshToken success response with exp claim', function () { + dapUtils.dapRefreshToken(sampleConfig, true, onDone) + let request = server.requests[0]; + let tokenWithExpiry = 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIiwiZXhwIjoxNjQzODMwMzY5fQ..hTbcSQgmmO0HUJJrQ5fRHw.7zjrQXNNVkb-GD0ZhIVhEPcWbyaDBilHTWv-bp1lFZ9mdkSC0QbcAvUbYteiTD7ya23GUwcL2WOW8WgRSHaWHOJe0B5NDqfdUGTzElWfu7fFodRxRgGmwG8Rq5xxteFKLLGHLf1mFYRJKDtjtgajGNUKIDfn9AEt-c5Qz4KU8VolG_KzrLROx-f6Z7MnoPTcwRCj0WjXD6j2D6RAZ80-mKTNIsMIELdj6xiabHcjDJ1WzwtwCZSE2y2nMs451pSYp8W-bFPfZmDDwrkjN4s9ASLlIXcXgxK-H0GsiEbckQOZ49zsIKyFtasBvZW8339rrXi1js-aBh99M7aS5w9DmXPpUDmppSPpwkeTfKiqF0cQiAUq8tpeEQrGDJuw3Qt2.XI8h9Xw-VZj_NOmKtV19wLM63S4snos7rzkoHf9FXCw' + responseHeader['Akamai-DAP-Token'] = tokenWithExpiry; + request.respond(200, responseHeader, JSON.stringify(tokenWithExpiry)); + expect(JSON.parse(storage.getDataFromLocalStorage(DAP_TOKEN)).expires_at).to.be.equal(1643830359); + }); + + it('test dapRefreshToken error response', function () { + storage.setDataInLocalStorage(DAP_TOKEN, JSON.stringify(sampleCachedToken)); + dapUtils.dapRefreshToken(sampleConfig, false, onDone) + let request = server.requests[0]; + request.respond(400, responseHeader, 'error'); + expect(JSON.parse(storage.getDataFromLocalStorage(DAP_TOKEN)).expires_at).to.be.equal(cacheExpiry);// Since the expiry is same, the token is not updated in the cache + }); + }); + + describe('dapRefreshMembership test', function () { + it('test dapRefreshMembership success response', function () { + let membership = {'cohorts': ['9', '11', '13'], 'said': 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIn0..17wnrhz6FbWx0Cf6LXpm1A.m9PKVCradk3CZokNKzVHzE06TOqiXYeijgxTQUiQy5Syx-yicnO8DyYX6zQ6rgPcNgUNRt4R4XE5MXuK0laUVQJr9yc9g3vUfQfw69OMYGW_vRlLMPzoNOhF2c4gSyfkRrLr7C0qgALmZO1D11sPflaCTNmO7pmZtRaCOB5buHoWcQhp1bUSJ09DNDb31dX3llimPwjNGSrUhyq_EZl4HopnnjxbM4qVNMY2G_43C_idlVOvbFoTxcDRATd-6MplJoIOIHQLDZEetpIOVcbEYN9gQ_ndBISITwuu5YEgs5C_WPHA25nm6e4BT5R-tawSA8yPyQAupqE8gk4ZWq_2-T0cqyTstIHrMQnZ_vysYN7h6bkzE-KeZRk7GMtySN87_fiu904hLD9QentGegamX6UAbVqQh7Htj7SnMHXkEenjxXAM5mRqQvNCTlw8k-9-VPXs-vTcKLYP8VFf8gMOmuYykgWac1gX-svyAg-24mo8cUbqcsj9relx4Qj5HiXUVyDMBZxK-mHZi-Xz6uv9GlggcsjE13DSszar-j2OetigpdibnJIxRZ-4ew3-vlvZ0Dul3j0LjeWURVBWYWfMjuZ193G7lwR3ohh_NzlNfwOPBK_SYurdAnLh7jJgTW-lVLjH2Dipmi9JwX9s03IQq9opexAn7hlM9oBI6x5asByH8JF8WwZ5GhzDjpDwpSmHPQNGFRSyrx_Sh2CPWNK6C1NJmLkyqAtJ5iw0_al7vPDQyZrKXaLTjBCUnbpJhUZ8dUKtWLzGPjzFXp10muoDIutd1NfyKxk1aWGhx5aerYuLdywv6cT_M8RZTi8924NGj5VA30V5OvEwLLyX93eDhntXZSCbkPHpAfiRZNGXrPY.GhCbWGQz11mIRD4uPKmoAuFXDH7hGnils54zg7N7-TU'} + dapUtils.dapRefreshMembership(sampleConfig, sampleCachedToken.token, onDone) + let request = server.requests[0]; + request.respond(200, responseHeader, JSON.stringify(membership)); + let rtdObj = dapUtils.dapGetRtdObj(membership, 503) + expect(config.getConfig().ortb2.user.data).to.deep.include.members(rtdObj.rtd.ortb2.user.data); + }); + + it('test dapRefreshMembership success response with exp claim', function () { + let membership = {'cohorts': ['9', '11', '13'], 'said': 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIiwiZXhwIjoxNjQ3OTcxNTU4fQ..ptdM5WO-62ypXlKxFXD4FQ.waEo9MHS2NYQCi-zh_p6HgT9BdqGyQbBq4GfGLfsay4nRBgICsTS-VkV6e7xx5U1T8BgpKkRJIZBwTOY5Pkxk9FpK5nnffDSEljRrp1LXLCkNP4qwrlqHInFbZsonNWW4_mW-7aUPlTwIsTbfjTuyHdXHeQa1ALrwFFFWE7QUmPNd2RsHjDwUsxlJPEb5TnHn5W0Mgo_PQZaxvhJInMbxPgtJLoqnJvOqCBEoQY7au7ALZL_nWK8XIwPMF19J7Z3cBg9vQInhr_E3rMdQcAFHEzYfgoNcIYCCR0t1UOqUE3HNtX-E64kZAYKWdlsBb9eW5Gj9hHYyPNL_4Hntjg5eLXGpsocMg0An-qQKGC6hkrxKzeM-GrjpvSaQLNs4iqDpHUtzA02LW_vkLkMNRUiyXVJ3FUZwfyq6uHSRKWZ6UFdAfL0rfJ8q8x8Ll-qJO2Jfyvidlsi9FIs7x1WJrvDCKepfAQM1UXRTonrQljFBAk83PcL2bmWuJDgJZ0lWS4VnZbIf6A7fDourmkDxdVRptvQq5nSjtzCA6whRw0-wGz8ehNJsaJw9H_nG9k4lRKs7A5Lqsyy7TVFrAPjnA_Q1a2H6xF2ULxrtIqoNqdX7k9RjowEZSQlZgZUOAmI4wzjckdcSyC_pUlYBMcBwmlld34mmOJe9EBHAxjdci7Q_9lvj1HTcwGDcQITXnkW9Ux5Jkt9Naw-IGGrnEIADaT2guUAto8W_Gb05TmwHSd6DCmh4zepQCbqeVe6AvPILtVkTgsTTo27Q-NvS7h-XtthJy8425j5kqwxxpZFJ0l0ytc6DUyNCLJXuxi0JFU6-LoSXcROEMVrHa_Achufr9vHIELwacSAIHuwseEvg_OOu1c1WYEwZH8ynBLSjqzy8AnDj24hYgA0YanPAvDqacrYrTUFqURbHmvcQqLBTcYa_gs7uDx4a1EjtP_NvHRlvCgGAaASrjGMhTX8oJxlTqahhQ.pXm-7KqnNK8sbyyczwkVYhcjgiwkpO8LjBBVw4lcyZE'} + dapUtils.dapRefreshMembership(sampleConfig, sampleCachedToken.token, onDone) + let request = server.requests[0]; + request.respond(200, responseHeader, JSON.stringify(membership)); + let rtdObj = dapUtils.dapGetRtdObj(membership, 503) + expect(config.getConfig().ortb2.user.data).to.deep.include.members(rtdObj.rtd.ortb2.user.data); + expect(JSON.parse(storage.getDataFromLocalStorage(DAP_MEMBERSHIP)).expires_at).to.be.equal(1647971548); + }); + + it('test dapRefreshMembership 400 error response', function () { + dapUtils.dapRefreshMembership(sampleConfig, sampleCachedToken.token, onDone) + let request = server.requests[0]; + request.respond(400, responseHeader, 'error'); + expect(config.getConfig().ortb2).to.be.equal(undefined); + }); + + it('test dapRefreshMembership 403 error response', function () { + dapUtils.dapRefreshMembership(sampleConfig, sampleCachedToken.token, onDone) + let request = server.requests[0]; + request.respond(403, responseHeader, 'error'); + let requestTokenize = server.requests[1]; + responseHeader['Akamai-DAP-Token'] = sampleCachedToken.token; + requestTokenize.respond(200, responseHeader, ''); + let requestMembership = server.requests[2]; + requestMembership.respond(403, responseHeader, 'error'); + expect(server.requests.length).to.be.equal(DAP_MAX_RETRY_TOKENIZE + 2); + }); + }); + + describe('dapRefreshEncryptedMembership test', function () { + it('test dapRefreshEncryptedMembership success response', function () { + let expiry = Math.round(Date.now() / 1000.0) + 3600; // in seconds + let encMembership = 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoic29tZXNlY3JldGludmF1bHQifQ..f8_At4OqeQXyQcSwThOJ_w.69ImVQ3bEZ6QP7ROCRpAJjNcKY49SEPYR6qTp_8l7L8kQdPbpi4wmuOzt78j7iBrX64k2wltzmQFjDmVKSxDhrEguxpgx6t-L1tT8ZA0UosMWpVsgmKEZxOn2e9ES3jw8RNCS4WSWocSPQX33xSb51evXjm9E1s0tGoLnwXl0GsUvzRsSU86wQG6RZnAQTi7s-r-M2TKibdDjUqgIt62vJ-aBZ7RWw91MINgOdmDNs1bFfbBX5Cy1kd4-kjvRDz_aJ6zHX4sK_7EmQhGEY3tW-A3_l2I88mw-RSJaPkb_IWg0QpVwXDaE2F2g8NpY1PzCRvG_NIE8r28eK5q44OMVitykHmKmBXGDj7z2JVgoXkfo5u0I-dypZARn4GP_7niK932avB-9JD7Mz3TrlU4GZ7IpYfJ91PMsRhrs5xNPQwLZbpuhF76A7Dp7iss71UjkGCiPTU6udfRb4foyf_7xEF66m1eQVcVaMdxEbMuu9GBfdr-d04TbtJhPfUV8JfxTenvRYoi13n0j5kH0M5OgaSQD9kQ3Mrd9u-Cms-BGtT0vf-N8AaFZY_wn0Y4rkpv5HEaH7z3iT4RCHINWrXb_D0WtjLTKQi2YmF8zMlzUOewNJGwZRwbRwxc7JoDIKEc5RZkJYevfJXOEEOPGXZ7AGZxOEsJawPqFqd_nOUosCZS4akHhcDPcVowoecVAV0hhhoS6JEY66PhPp1snbt6yqA-fQhch7z8Y-DZT3Scibvffww3Scg_KFANWp0KeEvHG0vyv9R2F4o66viSS8y21MDnM7Yjk8C-j7aNMldUQbjN_7Yq1nkfe0jiBX_hsINBRPgJHUY4zCaXuyXs-JZZfU92nwG0RT3A_3RP2rpY8-fXp9d3C2QJjEpnmHvTMsuAZCQSBe5DVrJwN_UKedxcJEoOt0wLz6MaCMyYZPd8tnQeqYK1cd3RgQDXtzKC0HDw1En489DqJXEst4eSSkaaW1lImLeaF8XCOaIqPqoyGk4_6KVLw5Q7OnpczuXqYKMd9UTMovGeuTuo1k0ddfEqTq9QwxkwZL51AiDRnwTCAeYBU1krV8FCJQx-mH_WPB5ftZj-o_3pbvANeRk27QBVmjcS-tgDllJkWBxX-4axRXzLw8pUUUZUT_NOL0OiqUCWVm0qMBEpgRQ57Se42-hkLMTzLhhGJOnVcaXU1j4ep-N7faNvbgREBjf_LgzvaWS90a2NJ9bB_J9FyXelhCN_AMLfdOS3fHkeWlZ0u0PMbn5DxXRMe0l9jB-2VJZhcPQRlWoYyoCO3l4F5ZmuQP5Xh9CU4tvSWih6jlwMDgdVWuTpdfPD5bx8ccog3JDq87enx-QtPzLU3gMgouNARJGgNwKS_GJSE1uPrt2oiqgZ3Z0u_I5MKvPdQPV3o-4rsaE730eB4OwAOF-mkGWpzy8Pbl-Qe5PR9mHBhuyJgZ-WDSCHl5yvet2kfO9mPXZlqBQ26fzTcUYH94MULAZn36og6w.3iKGv-Le-AvRmi26W1v6ibRLGbwKbCR92vs-a9t55hw'; + dapUtils.dapRefreshEncryptedMembership(esampleConfig, sampleCachedToken.token, onDone) + let request = server.requests[0]; + responseHeader['Akamai-DAP-Token'] = encMembership; + request.respond(200, responseHeader, encMembership); + let rtdObj = dapUtils.dapGetEncryptedRtdObj({'encryptedSegments': encMembership}, 504) + expect(config.getConfig().ortb2.user.data).to.deep.include.members(rtdObj.rtd.ortb2.user.data); + expect(JSON.parse(storage.getDataFromLocalStorage(DAP_ENCRYPTED_MEMBERSHIP)).expires_at).to.equal(expiry); + }); + + it('test dapRefreshEncryptedMembership success response with exp claim', function () { + let encMembership = 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoic29tZXNlY3JldGludmF1bHQiLCJleHAiOjE2NDM4MzA2NDB9..inYoxwht_aqTIWqGhEm_Gw.wDcCUOCwtqgnNUouaD723gKfm7X7bgkHgtiX4mr07P3tWk25PUQunmwTLhWBB5CYzzGIfIvveG_u4glNRLi_eRSQV4ihKKk1AN-BSSJ3d0CLAdY9I1WG5vX1VmopXyKnV90bl9SLNqnhg4Vxe6YU4ogTYxsKHuIN1EeIH4hpl-HbCQWQ1DQt4mB-MQF8V9AWTfU0D7sFMSK8f9qj6NGmf1__oHdHUlws0t5V2UAn_dhJexsuREK_gh65pczCuly5eEcziZ82LeP-nOhKWSRHB_tS_mKXrRU6_At_EVDgtfA3PSBJ6eQylCii6bTL42vZzz4jZhJv_3eLfRdKqpVT5CWNBzcDoQ2VcQgKgIBtPJ45KFfAYTQ6kdl21QMSjqtu8GTsv1lEZtrqHY6zRiG8_Mu28-PmjEw4LDdZmBDOeroue_MJD6wuE_jlE7J2iVdo8CkVnoRgzFwNbKBo7CK4z0WahV9rhuOm0LKAN5H0jF_gj696U-3fVTDTIb8ndNKNI2_xAhvWs00BFGtUtWgr8QGDGRTDCNGsDgnb_Vva9xCqVOyAE9O3Fq1QYl-tMA-KkBt3zzvmFFpOxpOyH-lUubKLKlsrxKc3GSyVEQ9DDLhrXXJgR5H5BSE4tjlK7p3ODF5qz0FHtIj7oDcgLazFO7z2MuFy2LjJmd3hKl6ujcfYEDiQ4D3pMIo7oiU33aFBD1YpzI4-WzNfJlUt1FoK0-DAXpbbV95s8p08GOD4q81rPw5hRADKJEr0QzrbDwplTWCzT2fKXMg_dIIc5AGqGKnVRUS6UyF1DnHpudNIJWxyWZjWIEw_QNjU0cDFmyPSyKxNrnfq9w8WE2bfbS5KTicxei5QHnC-cnL7Nh7IXp7WOW6R1YHbNPT7Ad4OhnlV-jjrXwkSv4wMAbfwAWoSCchGh7uvENNAeJymuponlJbOgw_GcYM73hMs8Z8W9qxRfbyF4WX5fDKXg61mMlaieHkc0EnoC5q7uKyXuZUehHZ76JLDFmewslLkQq5SkVCttzJePBnY1ouPEHw5ZTzUnG5f01QQOVcjIN-AqXNDbG5IOwq0heyS6vVfq7lZKJdLDVQ21qRjazGPaqYwLzugkWkzCOzPTgyFdbXzgjfmJwylHSOM5Jpnul84GzxEQF-1mHP2A8wtIT-M7_iX24It2wwWvc8qLA6GEqruWCtNyoug8CXo44mKdSSCGeEZHtfMbzXdLIBHCy2jSHz5i8S7DU_R7rE_5Ssrb81CqIYbgsAQBHtOYoyvzduTOruWcci4De0QcULloqImIEHUuIe2lnYO889_LIx5p7nE3UlSvLBo0sPexavFUtHqI6jdG6ye9tdseUEoNBDXW0aWD4D-KXX1JLtAgToPVUtEaXCJI7QavwO9ZG6UZM6jbfuJ5co0fvUXp6qYrFxPQo2dYHkar0nT6s1Zg5l2g8yWlLUJrHdHAzAw_NScUp71OpM4TmNsLnYaPVPcOxMvtJXTanbNWr0VKc8gy9q3k_1XxAnQwiduNs7f5bA-6qCVpayHv5dE7mUhFEwyh1_w95jEaURsQF_hnnd2OqRkADfiok4ZiPU2b38kFW1LXjpI39XXES3JU0e08Rq2uuelyLbCLWuJWq_axuKSZbZvpYeqWtIAde8FjCiO7RPlEc0nyzWBst8RBxQ-Bekg9UXPhxBRcm0HwA.Q2cBSFOQAC-QKDwmjrQXnVQd3jNOppMl9oZfd2yuKeY'; + dapUtils.dapRefreshEncryptedMembership(esampleConfig, sampleCachedToken.token, onDone) + let request = server.requests[0]; + responseHeader['Akamai-DAP-Token'] = encMembership; + request.respond(200, responseHeader, encMembership); + let rtdObj = dapUtils.dapGetEncryptedRtdObj({'encryptedSegments': encMembership}, 504) + expect(config.getConfig().ortb2.user.data).to.deep.include.members(rtdObj.rtd.ortb2.user.data); + expect(JSON.parse(storage.getDataFromLocalStorage(DAP_ENCRYPTED_MEMBERSHIP)).expires_at).to.equal(1643830630); + }); + + it('test dapRefreshEncryptedMembership error response', function () { + dapUtils.dapRefreshEncryptedMembership(esampleConfig, sampleCachedToken.token, onDone) + let request = server.requests[0]; + request.respond(400, responseHeader, 'error'); + expect(config.getConfig().ortb2).to.be.equal(undefined); + }); + + it('test dapRefreshEncryptedMembership 403 error response', function () { + generateRealTimeData({}, () => {}, emoduleConfig, {}); + dapUtils.dapRefreshEncryptedMembership(esampleConfig, sampleCachedToken.token, onDone) + let request = server.requests[0]; + request.respond(403, responseHeader, 'error'); + let requestTokenize = server.requests[1]; + responseHeader['Akamai-DAP-Token'] = sampleCachedToken.token; + requestTokenize.respond(200, responseHeader, ''); + let requestMembership = server.requests[2]; + requestMembership.respond(403, responseHeader, 'error'); + expect(server.requests.length).to.be.equal(DAP_MAX_RETRY_TOKENIZE + 2); + }); + }); + + describe('dapGetEncryptedMembershipFromLocalStorage test', function () { + it('test dapGetEncryptedMembershipFromLocalStorage function with valid cache', function () { + storage.setDataInLocalStorage(DAP_ENCRYPTED_MEMBERSHIP, JSON.stringify(cachedEncryptedMembership)) + expect(JSON.stringify(dapUtils.dapGetEncryptedMembershipFromLocalStorage())).to.equal(JSON.stringify(encMembership)); + }); + + it('test dapGetEncryptedMembershipFromLocalStorage function with invalid cache', function () { + let expiry = Math.round(Date.now() / 1000.0) - 100; // in seconds + let encMembership = {'expiry': expiry, 'encryptedSegments': cachedEncryptedMembership.encryptedSegments} + storage.setDataInLocalStorage(DAP_ENCRYPTED_MEMBERSHIP, JSON.stringify(encMembership)) + expect(dapUtils.dapGetEncryptedMembershipFromLocalStorage()).to.equal(null); + }); + }); + + describe('Akamai-DAP-SS-ID test', function () { + it('Akamai-DAP-SS-ID present in response header', function () { + let expiry = Math.round(Date.now() / 1000.0) + 300; // in seconds + dapUtils.dapRefreshToken(sampleConfig, false, onDone) + let request = server.requests[0]; + let sampleSSID = 'Test_SSID_Spec'; + responseHeader['Akamai-DAP-Token'] = sampleCachedToken.token; + responseHeader['Akamai-DAP-SS-ID'] = sampleSSID; + request.respond(200, responseHeader, ''); + expect(storage.getDataFromLocalStorage(DAP_SS_ID)).to.be.equal(JSON.stringify(sampleSSID)); + }); + + it('Test if Akamai-DAP-SS-ID is present in request header', function () { + let expiry = Math.round(Date.now() / 1000.0) + 100; // in seconds + storage.setDataInLocalStorage(DAP_SS_ID, JSON.stringify('Test_SSID_Spec')) + dapUtils.dapRefreshToken(sampleConfig, false, onDone) + let request = server.requests[0]; + let ssidHeader = request.requestHeaders['Akamai-DAP-SS-ID']; + responseHeader['Akamai-DAP-Token'] = sampleCachedToken.token; + request.respond(200, responseHeader, ''); + expect(ssidHeader).to.be.equal('Test_SSID_Spec'); + }); + }); + + describe('Test gdpr and usp consent handling', function () { + it('Gdpr applies and gdpr consent string not present', function () { + expect(akamaiDapRtdSubmodule.init(null, sampleGdprConsentConfig)).to.equal(false); + }); + + it('Gdpr applies and gdpr consent string is present', function () { + sampleGdprConsentConfig.gdpr.consentString = 'BOJ/P2HOJ/P2HABABMAAAAAZ+A=='; + expect(akamaiDapRtdSubmodule.init(null, sampleGdprConsentConfig)).to.equal(true); + }); + + it('USP consent present and user have opted out', function () { + expect(akamaiDapRtdSubmodule.init(null, sampleUspConsentConfig)).to.equal(false); + }); + + it('USP consent present and user have not been provided with option to opt out', function () { + expect(akamaiDapRtdSubmodule.init(null, {'usp': '1NYY'})).to.equal(false); + }); + + it('USP consent present and user have not opted out', function () { + expect(akamaiDapRtdSubmodule.init(null, {'usp': '1YNY'})).to.equal(true); }); }); }); From f39d84981041d8fea40bc885a7cb168dd6967bc9 Mon Sep 17 00:00:00 2001 From: Chris Pabst Date: Thu, 26 May 2022 07:10:01 -0600 Subject: [PATCH 44/76] SOVRN Bid Adapter: refactor old test code (#8430) * feat: [EX-3265] format tests for prebid.js adapter * feat: [EX-3265] add tests for video media type * feat: [EX-3265] fix test for interpretResponse * feat: [EX-3265] fix test for interpretResponse * feat: [EX-3265] fix test for interpretResponse Co-authored-by: Maxim Pakhomov Co-authored-by: Maxim Pakhomov <70204615+maximpakhomov@users.noreply.github.com> --- test/spec/modules/sovrnBidAdapter_spec.js | 517 +++++++++++----------- 1 file changed, 253 insertions(+), 264 deletions(-) diff --git a/test/spec/modules/sovrnBidAdapter_spec.js b/test/spec/modules/sovrnBidAdapter_spec.js index 83a13f0db7b..c515f302c32 100644 --- a/test/spec/modules/sovrnBidAdapter_spec.js +++ b/test/spec/modules/sovrnBidAdapter_spec.js @@ -1,92 +1,91 @@ -import {expect} from 'chai'; -import {spec} from 'modules/sovrnBidAdapter.js'; -import {config} from 'src/config.js'; +import {expect} from 'chai' +import {spec} from 'modules/sovrnBidAdapter.js' +import {config} from 'src/config.js' import * as utils from 'src/utils.js' -const ENDPOINT = `https://ap.lijit.com/rtb/bid?src=$$REPO_AND_VERSION$$`; +const ENDPOINT = `https://ap.lijit.com/rtb/bid?src=$$REPO_AND_VERSION$$` const baseBidRequest = { - 'bidder': 'sovrn', - 'params': { - 'tagid': 403370 + bidder: 'sovrn', + params: { + tagid: 403370 }, - 'adUnitCode': 'adunit-code', - 'sizes': [ + adUnitCode: 'adunit-code', + sizes: [ [300, 250], [300, 600] ], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', } const baseBidderRequest = { refererInfo: { referer: 'http://example.com/page.html', } -}; +} describe('sovrnBidAdapter', function() { describe('isBidRequestValid', function () { it('should return true when required params found', function () { - expect(spec.isBidRequestValid(baseBidRequest)).to.equal(true); - }); + expect(spec.isBidRequestValid(baseBidRequest)).to.equal(true) + }) it('should return false when tagid not passed correctly', function () { const bidRequest = { ...baseBidRequest, - 'params': { + params: { ...baseBidRequest.params, - 'tagid': 'ABCD' + tagid: 'ABCD' }, - }; + } - expect(spec.isBidRequestValid(bidRequest)).to.equal(false); - }); + expect(spec.isBidRequestValid(bidRequest)).to.equal(false) + }) it('should return false when require params are not passed', function () { const bidRequest = { ...baseBidRequest, - 'params': {} + params: {} } - expect(spec.isBidRequestValid(bidRequest)).to.equal(false); - }); + expect(spec.isBidRequestValid(bidRequest)).to.equal(false) + }) it('should return false when require video params are not passed', function () { const bidRequest = { ...baseBidRequest, - 'mediaTypes': { + mediaTypes: { 'video': { } } } - expect(spec.isBidRequestValid(bidRequest)).to.equal(false); - }); - }); + expect(spec.isBidRequestValid(bidRequest)).to.equal(false) + }) + }) describe('buildRequests', function () { describe('basic bid parameters', function() { - const request = spec.buildRequests([baseBidRequest], baseBidderRequest); - const payload = JSON.parse(request.data); + const request = spec.buildRequests([baseBidRequest], baseBidderRequest) + const payload = JSON.parse(request.data) it('sends bid request to our endpoint via POST', function () { - expect(request.method).to.equal('POST'); - }); + expect(request.method).to.equal('POST') + }) it('attaches source and version to endpoint URL as query params', function () { expect(request.url).to.equal(ENDPOINT) - }); + }) it('sets the proper banner object', function() { const bannerBidRequest = { ...baseBidRequest, - 'mediaTypes': { + mediaTypes: { banner: {} } } const request = spec.buildRequests([bannerBidRequest], baseBidderRequest) - const payload = JSON.parse(request.data) const impression = payload.imp[0] @@ -105,7 +104,7 @@ describe('sovrnBidAdapter', function() { const startdelay = 0 const videoBidRequest = { ...baseBidRequest, - 'mediaTypes': { + mediaTypes: { video: { mimes, protocols, @@ -117,7 +116,6 @@ describe('sovrnBidAdapter', function() { } } const request = spec.buildRequests([videoBidRequest], baseBidderRequest) - const payload = JSON.parse(request.data) const impression = payload.imp[0] @@ -131,9 +129,9 @@ describe('sovrnBidAdapter', function() { }) it('gets correct site info', function() { - expect(payload.site.page).to.equal('http://example.com/page.html'); - expect(payload.site.domain).to.equal('example.com'); - }); + expect(payload.site.page).to.equal('http://example.com/page.html') + expect(payload.site.domain).to.equal('example.com') + }) it('includes the ad unit code in the request', function() { const impression = payload.imp[0] @@ -142,22 +140,21 @@ describe('sovrnBidAdapter', function() { it('converts tagid to string', function () { expect(request.data).to.contain('"tagid":"403370"') - }); + }) }) it('accepts a single array as a size', function() { const singleSizeBidRequest = { ...baseBidRequest, - 'params': { - 'iv': 'vet' + params: { + iv: 'vet' }, - 'sizes': [300, 250], - 'mediaTypes': { + sizes: [300, 250], + mediaTypes: { banner: {} }, } const request = spec.buildRequests([singleSizeBidRequest], baseBidderRequest) - const payload = JSON.parse(request.data) const impression = payload.imp[0] @@ -176,22 +173,21 @@ describe('sovrnBidAdapter', function() { const request = spec.buildRequests([ivBidRequest], baseBidderRequest) expect(request.url).to.contain('iv=vet') - }); + }) it('sends gdpr info if exists', function () { const bidderRequest = { ...baseBidderRequest, - 'bidderCode': 'sovrn', - 'auctionId': '1d1a030790a475', - 'bidderRequestId': '22edbae2733bf6', - 'timeout': 3000, - 'gdprConsent': { - 'consentString': 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A==', - 'gdprApplies': true + bidderCode: 'sovrn', + auctionId: '1d1a030790a475', + bidderRequestId: '22edbae2733bf6', + timeout: 3000, + gdprConsent: { + consentString: 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A==', + gdprApplies: true }, - 'bids': [baseBidRequest] - }; - + bids: [baseBidRequest] + } const { regs, user } = JSON.parse(spec.buildRequests([baseBidRequest], bidderRequest).data) expect(regs.ext.gdpr).to.exist.and.to.be.a('number') @@ -203,14 +199,13 @@ describe('sovrnBidAdapter', function() { it('should send us_privacy if bidderRequest has a value for uspConsent', function () { const bidderRequest = { ...baseBidderRequest, - 'bidderCode': 'sovrn', - 'auctionId': '1d1a030790a475', - 'bidderRequestId': '22edbae2733bf6', - 'timeout': 3000, - 'uspConsent': '1NYN', - 'bids': [baseBidRequest] + bidderCode: 'sovrn', + auctionId: '1d1a030790a475', + bidderRequestId: '22edbae2733bf6', + timeout: 3000, + uspConsent: '1NYN', + bids: [baseBidRequest] } - const data = JSON.parse(spec.buildRequests([baseBidRequest], bidderRequest).data) expect(data.regs.ext['us_privacy']).to.equal(bidderRequest.uspConsent) @@ -219,21 +214,20 @@ describe('sovrnBidAdapter', function() { it('should add schain if present', function() { const schainRequest = { ...baseBidRequest, - 'schain': { - 'ver': '1.0', - 'complete': 1, - 'nodes': [ + schain: { + ver: '1.0', + complete: 1, + nodes: [ { - 'asi': 'directseller.com', - 'sid': '00001', - 'rid': 'BidRequest1', - 'hp': 1 + asi: 'directseller.com', + sid: '00001', + rid: 'BidRequest1', + hp: 1 } ] } } const schainRequests = [schainRequest, baseBidRequest] - const data = JSON.parse(spec.buildRequests(schainRequests, baseBidderRequest).data) expect(data.source.ext.schain.nodes.length).to.equal(1) @@ -248,7 +242,6 @@ describe('sovrnBidAdapter', function() { } } const criteoIdRequests = [criteoIdRequest, baseBidRequest] - const ext = JSON.parse(spec.buildRequests(criteoIdRequests, baseBidderRequest).data).user.ext const firstEID = ext.eids[0] const secondEID = ext.eids[1] @@ -294,7 +287,6 @@ describe('sovrnBidAdapter', function() { bidfloor: 2.00 } } - const request = spec.buildRequests([floorBid], baseBidderRequest) const payload = JSON.parse(request.data) @@ -309,7 +301,6 @@ describe('sovrnBidAdapter', function() { tagid: 1234, bidfloor: 2.00 } - const request = spec.buildRequests([floorBid], baseBidderRequest) const impression = JSON.parse(request.data).imp[0] @@ -359,7 +350,6 @@ describe('sovrnBidAdapter', function() { } } } - const request = spec.buildRequests([fpdBidRequest], baseBidderRequest) const payload = JSON.parse(request.data) @@ -381,7 +371,6 @@ describe('sovrnBidAdapter', function() { } } } - const request = spec.buildRequests([fpdBid], baseBidderRequest) const impression = JSON.parse(request.data).imp[0] @@ -390,102 +379,109 @@ describe('sovrnBidAdapter', function() { expect(impression.ext.deals).to.deep.equal(['seg1', 'seg2']) }) }) - }); + }) describe('interpretResponse', function () { - let response; + let response const baseResponse = { - 'requestId': '263c448586f5a1', - 'cpm': 0.45882675, - 'width': 728, - 'height': 90, - 'creativeId': 'creativelycreatedcreativecreative', - 'dealId': null, - 'currency': 'USD', - 'netRevenue': true, - 'mediaType': 'banner', - 'ttl': 90, - 'meta': { advertiserDomains: [] }, - 'ad': decodeURIComponent(``), + requestId: '263c448586f5a1', + cpm: 0.45882675, + width: 728, + height: 90, + creativeId: 'creativelycreatedcreativecreative', + dealId: null, + currency: 'USD', + netRevenue: true, + mediaType: 'banner', + ttl: 90, + meta: { advertiserDomains: [] }, + ad: decodeURIComponent(``), } const videoBid = { - 'id': 'a_403370_332fdb9b064040ddbec05891bd13ab28', - 'crid': 'creativelycreatedcreativecreative', - 'impid': '263c448586f5a1', - 'price': 0.45882675, - 'nurl': '', - 'adm': 'key%3Dvalue', - 'h': 480, - 'w': 640 + id: 'a_403370_332fdb9b064040ddbec05891bd13ab28', + crid: 'creativelycreatedcreativecreative', + impid: '263c448586f5a1', + price: 0.45882675, + nurl: '', + adm: 'key%3Dvalue', + h: 480, + w: 640 } const bannerBid = { - 'id': 'a_403370_332fdb9b064040ddbec05891bd13ab28', - 'crid': 'creativelycreatedcreativecreative', - 'impid': '263c448586f5a1', - 'price': 0.45882675, - 'nurl': '', - 'adm': '', - 'h': 90, - 'w': 728 + id: 'a_403370_332fdb9b064040ddbec05891bd13ab28', + crid: 'creativelycreatedcreativecreative', + impid: '263c448586f5a1', + price: 0.45882675, + nurl: '', + adm: '', + h: 90, + w: 728 } + beforeEach(function () { response = { body: { - 'id': '37386aade21a71', - 'seatbid': [{ - 'bid': [{ + id: '37386aade21a71', + seatbid: [{ + bid: [{ ...bannerBid }] }] } - }; - }); + } + }) it('should get the correct bid response', function () { const expectedResponse = { - ...baseResponse, - 'ttl': 60000, - }; - - const result = spec.interpretResponse(response); + requestId: '263c448586f5a1', + cpm: 0.45882675, + width: 728, + height: 90, + creativeId: 'creativelycreatedcreativecreative', + dealId: null, + currency: 'USD', + netRevenue: true, + mediaType: 'banner', + ttl: 60000, + meta: { advertiserDomains: [] }, + ad: decodeURIComponent(`>`) + } + const result = spec.interpretResponse(response) - expect(result[0]).to.have.deep.keys(expectedResponse) - }); + expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse)) + }) it('crid should default to the bid id if not on the response', function () { - delete response.body.seatbid[0].bid[0].crid; + delete response.body.seatbid[0].bid[0].crid const expectedResponse = { ...baseResponse, - 'creativeId': response.body.seatbid[0].bid[0].id, - 'ad': decodeURIComponent(``), + creativeId: response.body.seatbid[0].bid[0].id, + ad: decodeURIComponent(``), } + const result = spec.interpretResponse(response) - const result = spec.interpretResponse(response); - - expect(result[0]).to.deep.equal(expectedResponse); - }); + expect(result[0]).to.deep.equal(expectedResponse) + }) it('should get correct bid response when dealId is passed', function () { - response.body.seatbid[0].bid[0].dealid = 'baking'; + response.body.seatbid[0].bid[0].dealid = 'baking' const expectedResponse = { ...baseResponse, - 'dealId': 'baking', + dealId: 'baking', } - const result = spec.interpretResponse(response) - expect(result[0]).to.deep.equal(expectedResponse); - }); + expect(result[0]).to.deep.equal(expectedResponse) + }) it('should get correct bid response when ttl is set', function () { - response.body.seatbid[0].bid[0].ext = { 'ttl': 480 } + response.body.seatbid[0].bid[0].ext = { ttl: 480 } const expectedResponse = { ...baseResponse, - 'ttl': 480, + ttl: 480, } - const result = spec.interpretResponse(response) expect(result[0]).to.deep.equal(expectedResponse) @@ -494,20 +490,19 @@ describe('sovrnBidAdapter', function() { it('handles empty bid response', function () { const response = { body: { - 'id': '37386aade21a71', - 'seatbid': [] + id: '37386aade21a71', + seatbid: [] } - }; - + } const result = spec.interpretResponse(response) - expect(result.length).to.equal(0); - }); + expect(result.length).to.equal(0) + }) it('should get the correct bid response with 2 different bids', function () { const expectedVideoResponse = { ...baseResponse, - 'vastXml': decodeURIComponent(videoBid.adm) + vastXml: decodeURIComponent(videoBid.adm) } delete expectedVideoResponse.ad @@ -533,90 +528,87 @@ describe('sovrnBidAdapter', function() { expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse)) expect(Object.keys(result[1])).to.deep.equal(Object.keys(expectedResponse)) }) - }); + }) describe('interpretResponse video', function () { - let videoResponse; - const bidAdm = 'key%3Dvalue'; - const decodedBidAdm = decodeURIComponent(bidAdm); + let videoResponse + const bidAdm = 'key%3Dvalue' + const decodedBidAdm = decodeURIComponent(bidAdm) const baseVideoResponse = { - 'requestId': '263c448586f5a1', - 'cpm': 0.45882675, - 'width': 640, - 'height': 480, - 'creativeId': 'creativelycreatedcreativecreative', - 'dealId': null, - 'currency': 'USD', - 'netRevenue': true, - 'mediaType': 'video', - 'ttl': 90, - 'meta': { advertiserDomains: [] }, - 'vastXml': decodedBidAdm + requestId: '263c448586f5a1', + cpm: 0.45882675, + width: 640, + height: 480, + creativeId: 'creativelycreatedcreativecreative', + dealId: null, + currency: 'USD', + netRevenue: true, + mediaType: 'video', + ttl: 90, + meta: { advertiserDomains: [] }, + vastXml: decodedBidAdm } + beforeEach(function () { videoResponse = { body: { - 'id': '37386aade21a71', - 'seatbid': [{ - 'bid': [{ - 'id': 'a_403370_332fdb9b064040ddbec05891bd13ab28', - 'crid': 'creativelycreatedcreativecreative', - 'impid': '263c448586f5a1', - 'price': 0.45882675, - 'nurl': '', - 'adm': bidAdm, - 'h': 480, - 'w': 640 + id: '37386aade21a71', + seatbid: [{ + bid: [{ + id: 'a_403370_332fdb9b064040ddbec05891bd13ab28', + crid: 'creativelycreatedcreativecreative', + impid: '263c448586f5a1', + price: 0.45882675, + nurl: '', + adm: bidAdm, + h: 480, + w: 640 }] }] } - }; - }); + } + }) it('should get the correct bid response', function () { const expectedResponse = { ...baseVideoResponse, - 'ttl': 60000, - }; - - const result = spec.interpretResponse(videoResponse); + ttl: 60000, + } + const result = spec.interpretResponse(videoResponse) expect(result[0]).to.have.deep.keys(expectedResponse) - }); + }) it('crid should default to the bid id if not on the response', function () { - delete videoResponse.body.seatbid[0].bid[0].crid; + delete videoResponse.body.seatbid[0].bid[0].crid const expectedResponse = { ...baseVideoResponse, - 'creativeId': videoResponse.body.seatbid[0].bid[0].id, + creativeId: videoResponse.body.seatbid[0].bid[0].id, } + const result = spec.interpretResponse(videoResponse) - const result = spec.interpretResponse(videoResponse); - - expect(result[0]).to.deep.equal(expectedResponse); - }); + expect(result[0]).to.deep.equal(expectedResponse) + }) it('should get correct bid response when dealId is passed', function () { - videoResponse.body.seatbid[0].bid[0].dealid = 'baking'; + videoResponse.body.seatbid[0].bid[0].dealid = 'baking' const expectedResponse = { ...baseVideoResponse, - 'dealId': 'baking', + dealId: 'baking', } - const result = spec.interpretResponse(videoResponse) - expect(result[0]).to.deep.equal(expectedResponse); - }); + expect(result[0]).to.deep.equal(expectedResponse) + }) it('should get correct bid response when ttl is set', function () { videoResponse.body.seatbid[0].bid[0].ext = { 'ttl': 480 } const expectedResponse = { ...baseVideoResponse, - 'ttl': 480, + ttl: 480, } - const result = spec.interpretResponse(videoResponse) expect(result[0]).to.deep.equal(expectedResponse) @@ -625,52 +617,51 @@ describe('sovrnBidAdapter', function() { it('handles empty bid response', function () { const response = { body: { - 'id': '37386aade21a71', - 'seatbid': [] + id: '37386aade21a71', + seatbid: [] } - }; - + } const result = spec.interpretResponse(response) - expect(result.length).to.equal(0); - }); - }); + expect(result.length).to.equal(0) + }) + }) describe('getUserSyncs ', function() { - const syncOptions = { iframeEnabled: true, pixelEnabled: false }; - const iframeDisabledSyncOptions = { iframeEnabled: false, pixelEnabled: false }; + const syncOptions = { iframeEnabled: true, pixelEnabled: false } + const iframeDisabledSyncOptions = { iframeEnabled: false, pixelEnabled: false } const serverResponse = [ { - 'body': { - 'id': '546956d68c757f', - 'seatbid': [ + body: { + id: '546956d68c757f', + seatbid: [ { - 'bid': [ + bid: [ { - 'id': 'a_448326_16c2ada014224bee815a90d2248322f5', - 'impid': '2a3826aae345f4', - 'price': 1.0099999904632568, - 'nurl': 'http://localhost/rtb/impression?bannerid=220958&campaignid=3890&rtb_tid=15588614-75d2-40ab-b27e-13d2127b3c2e&rpid=1295&seatid=seat1&zoneid=448326&cb=26900712&tid=a_448326_16c2ada014224bee815a90d2248322f5', - 'adm': 'yo a creative', - 'crid': 'cridprebidrtb', - 'w': 160, - 'h': 600 + id: 'a_448326_16c2ada014224bee815a90d2248322f5', + impid: '2a3826aae345f4', + price: 1.0099999904632568, + nurl: 'http://localhost/rtb/impression?bannerid=220958&campaignid=3890&rtb_tid=15588614-75d2-40ab-b27e-13d2127b3c2e&rpid=1295&seatid=seat1&zoneid=448326&cb=26900712&tid=a_448326_16c2ada014224bee815a90d2248322f5', + adm: 'yo a creative', + crid: 'cridprebidrtb', + w: 160, + h: 600 }, { - 'id': 'a_430392_beac4c1515da4576acf6cb9c5340b40c', - 'impid': '3cf96fd26ed4c5', - 'price': 1.0099999904632568, - 'nurl': 'http://localhost/rtb/impression?bannerid=220957&campaignid=3890&rtb_tid=5bc0e68b-3492-448d-a6f9-26fa3fd0b646&rpid=1295&seatid=seat1&zoneid=430392&cb=62735099&tid=a_430392_beac4c1515da4576acf6cb9c5340b40c', - 'adm': 'yo a creative', - 'crid': 'cridprebidrtb', - 'w': 300, - 'h': 250 + id: 'a_430392_beac4c1515da4576acf6cb9c5340b40c', + impid: '3cf96fd26ed4c5', + price: 1.0099999904632568, + nurl: 'http://localhost/rtb/impression?bannerid=220957&campaignid=3890&rtb_tid=5bc0e68b-3492-448d-a6f9-26fa3fd0b646&rpid=1295&seatid=seat1&zoneid=430392&cb=62735099&tid=a_430392_beac4c1515da4576acf6cb9c5340b40c', + adm: 'yo a creative', + crid: 'cridprebidrtb', + w: 300, + h: 250 }, ] } ], - 'ext': { - 'iid': 13487408, + ext: { + iid: 13487408, sync: { pixels: [ { @@ -683,20 +674,19 @@ describe('sovrnBidAdapter', function() { } } }, - 'headers': {} + headers: {} } - ]; + ] it('should return if iid present on server response & iframe syncs enabled', function() { const expectedReturnStatement = { - 'type': 'iframe', - 'url': 'https://ap.lijit.com/beacon?informer=13487408', + type: 'iframe', + url: 'https://ap.lijit.com/beacon?informer=13487408', } + const returnStatement = spec.getUserSyncs(syncOptions, serverResponse) - const returnStatement = spec.getUserSyncs(syncOptions, serverResponse); - - expect(returnStatement[0]).to.deep.equal(expectedReturnStatement); - }); + expect(returnStatement[0]).to.deep.equal(expectedReturnStatement) + }) it('should include gdpr consent string if present', function() { const gdprConsent = { @@ -704,64 +694,63 @@ describe('sovrnBidAdapter', function() { consentString: 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A==' } const expectedReturnStatement = { - 'type': 'iframe', - 'url': `https://ap.lijit.com/beacon?gdpr_consent=${gdprConsent.consentString}&informer=13487408`, + type: 'iframe', + url: `https://ap.lijit.com/beacon?gdpr_consent=${gdprConsent.consentString}&informer=13487408`, } - const returnStatement = spec.getUserSyncs(syncOptions, serverResponse, gdprConsent, ''); + const returnStatement = spec.getUserSyncs(syncOptions, serverResponse, gdprConsent, '') - expect(returnStatement[0]).to.deep.equal(expectedReturnStatement); - }); + expect(returnStatement[0]).to.deep.equal(expectedReturnStatement) + }) it('should include us privacy string if present', function() { - const uspString = '1NYN'; + const uspString = '1NYN' const expectedReturnStatement = { - 'type': 'iframe', - 'url': `https://ap.lijit.com/beacon?us_privacy=${uspString}&informer=13487408`, + type: 'iframe', + url: `https://ap.lijit.com/beacon?us_privacy=${uspString}&informer=13487408`, } - const returnStatement = spec.getUserSyncs(syncOptions, serverResponse, null, uspString); + const returnStatement = spec.getUserSyncs(syncOptions, serverResponse, null, uspString) - expect(returnStatement[0]).to.deep.equal(expectedReturnStatement); - }); + expect(returnStatement[0]).to.deep.equal(expectedReturnStatement) + }) it('should include all privacy strings if present', function() { const gdprConsent = { gdprApplies: 1, consentString: 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A==' } - const uspString = '1NYN'; + const uspString = '1NYN' const expectedReturnStatement = { - 'type': 'iframe', - 'url': `https://ap.lijit.com/beacon?gdpr_consent=${gdprConsent.consentString}&us_privacy=${uspString}&informer=13487408`, + type: 'iframe', + url: `https://ap.lijit.com/beacon?gdpr_consent=${gdprConsent.consentString}&us_privacy=${uspString}&informer=13487408`, } const returnStatement = spec.getUserSyncs(syncOptions, serverResponse, gdprConsent, uspString) expect(returnStatement[0]).to.deep.equal(expectedReturnStatement) - }); + }) it('should not return if iid missing on server response', function() { - const returnStatement = spec.getUserSyncs(syncOptions, []); + const returnStatement = spec.getUserSyncs(syncOptions, []) - expect(returnStatement).to.be.empty; - }); + expect(returnStatement).to.be.empty + }) it('should not return if iframe syncs disabled', function() { - const returnStatement = spec.getUserSyncs(iframeDisabledSyncOptions, serverResponse); + const returnStatement = spec.getUserSyncs(iframeDisabledSyncOptions, serverResponse) - expect(returnStatement).to.be.empty; - }); + expect(returnStatement).to.be.empty + }) it('should include pixel syncs', function() { const pixelEnabledOptions = { iframeEnabled: false, pixelEnabled: true } - const otherResponce = { ...serverResponse, - 'body': { + body: { ...serverResponse.body, - 'ext': { - 'iid': 13487408, + ext: { + iid: 13487408, sync: { pixels: [ { @@ -778,33 +767,33 @@ describe('sovrnBidAdapter', function() { const returnStatement = spec.getUserSyncs(pixelEnabledOptions, [...serverResponse, otherResponce]) - expect(returnStatement.length).to.equal(4); + expect(returnStatement.length).to.equal(4) expect(returnStatement).to.deep.include.members([ { type: 'image', url: 'http://idprovider1.com' }, { type: 'image', url: 'http://idprovider2.com' }, { type: 'image', url: 'http://idprovider3.com' }, { type: 'image', url: 'http://idprovider4.com' } - ]); + ]) }) }) describe('prebid 3 upgrade', function() { const bidRequest = { ...baseBidRequest, - 'params': { - 'tagid': '403370' + params: { + tagid: '403370' }, - 'mediaTypes': { - 'banner': { - 'sizes': [ + mediaTypes: { + banner: { + sizes: [ [300, 250], [300, 600] ] } }, - }; - const request = spec.buildRequests([bidRequest], baseBidderRequest); - const payload = JSON.parse(request.data); + } + const request = spec.buildRequests([bidRequest], baseBidderRequest) + const payload = JSON.parse(request.data) it('gets sizes from mediaTypes.banner', function() { expect(payload.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}, {w: 300, h: 600}]) @@ -813,8 +802,8 @@ describe('sovrnBidAdapter', function() { }) it('gets correct site info', function() { - expect(payload.site.page).to.equal('http://example.com/page.html'); - expect(payload.site.domain).to.equal('example.com'); + expect(payload.site.page).to.equal('http://example.com/page.html') + expect(payload.site.domain).to.equal('example.com') }) }) }) From 7576a67e92b3b37dbd9d1f4aec1332ae2b2a7147 Mon Sep 17 00:00:00 2001 From: asurovenko-zeta <80847074+asurovenko-zeta@users.noreply.github.com> Date: Thu, 26 May 2022 20:35:39 +0700 Subject: [PATCH 45/76] Tests: remove console.log (#8481) Co-authored-by: Surovenko Alexey --- modules/relaidoBidAdapter.js | 3 --- test/spec/modules/proxistoreBidAdapter_spec.js | 2 -- 2 files changed, 5 deletions(-) diff --git a/modules/relaidoBidAdapter.js b/modules/relaidoBidAdapter.js index db381555ef9..8312dacbdbb 100644 --- a/modules/relaidoBidAdapter.js +++ b/modules/relaidoBidAdapter.js @@ -151,9 +151,6 @@ function interpretResponse(serverResponse, bidRequest) { } bidResponses.push(bidResponse); } - - // eslint-disable-next-line no-console - console.log(JSON.stringify(bidResponses)); return bidResponses; } diff --git a/test/spec/modules/proxistoreBidAdapter_spec.js b/test/spec/modules/proxistoreBidAdapter_spec.js index 084c533b5b5..5b9f35c5bcf 100644 --- a/test/spec/modules/proxistoreBidAdapter_spec.js +++ b/test/spec/modules/proxistoreBidAdapter_spec.js @@ -124,8 +124,6 @@ describe('ProxistoreBidAdapter', function () { bid.params['bidFloor'] = 1; let req = spec.buildRequests([bid], bidderRequest); data = JSON.parse(req.data); - // eslint-disable-next-line no-console - console.log(data.bids[0]); expect(data.bids[0].floor).equal(1); bid.getFloor = function () { return { currency: 'USD', floor: 1.0 }; From a7891db4866fa43f4594126924e392c7e8cff2a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bendeg=C3=BAz=20=C3=81cs?= <30595431+acsbendi@users.noreply.github.com> Date: Thu, 26 May 2022 16:18:34 +0200 Subject: [PATCH 46/76] Kobler adapter: remove outdated parameters, simplify testing (#8445) * Removed position parameter. * Removed zip parameter. * Removed placementId parameter and make sizes required instead. * Updated price-related macros. * Fixed error when params is not provided. * Removed last occurrence of placementId. * Read currency.adServerCurrency as publisherCurrency. * Use DEV endpoint for testing. * Use config.pageUrl when test is set to true. * Added more details about page URL. * `config.pageUrl`. * Added a comment explaining why pageUrl is considered only when testing. * Fixed double quotes in tests. --- modules/koblerBidAdapter.js | 99 +++---- modules/koblerBidAdapter.md | 29 +-- test/spec/modules/koblerBidAdapter_spec.js | 284 ++++++++++++--------- 3 files changed, 226 insertions(+), 186 deletions(-) diff --git a/modules/koblerBidAdapter.js b/modules/koblerBidAdapter.js index 80aa038a9f7..5fc28c47ac5 100644 --- a/modules/koblerBidAdapter.js +++ b/modules/koblerBidAdapter.js @@ -1,4 +1,12 @@ -import { deepAccess, isStr, replaceAuctionPrice, triggerPixel, isArray, parseQueryStringParameters, getWindowSelf } from '../src/utils.js'; +import { + deepAccess, + getWindowSelf, + isArray, + isStr, + parseQueryStringParameters, + replaceAuctionPrice, + triggerPixel +} from '../src/utils.js'; import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER} from '../src/mediaTypes.js'; @@ -6,19 +14,26 @@ import {getRefererInfo} from '../src/refererDetection.js'; const BIDDER_CODE = 'kobler'; const BIDDER_ENDPOINT = 'https://bid.essrtb.com/bid/prebid_rtb_call'; +const DEV_BIDDER_ENDPOINT = 'https://bid-service.dev.essrtb.com/bid/prebid_rtb_call'; const TIMEOUT_NOTIFICATION_ENDPOINT = 'https://bid.essrtb.com/notify/prebid_timeout'; const SUPPORTED_CURRENCY = 'USD'; const DEFAULT_TIMEOUT = 1000; const TIME_TO_LIVE_IN_SECONDS = 10 * 60; export const isBidRequestValid = function (bid) { - return !!(bid && bid.bidId && bid.params && bid.params.placementId); + if (!bid || !bid.bidId) { + return false; + } + + const sizes = deepAccess(bid, 'mediaTypes.banner.sizes', bid.sizes); + return isArray(sizes) && sizes.length > 0; }; export const buildRequests = function (validBidRequests, bidderRequest) { + const bidderEndpoint = isTest(validBidRequests[0]) ? DEV_BIDDER_ENDPOINT : BIDDER_ENDPOINT; return { method: 'POST', - url: BIDDER_ENDPOINT, + url: bidderEndpoint, data: buildOpenRtbBidRequestPayload(validBidRequests, bidderRequest), options: { contentType: 'application/json' @@ -27,14 +42,11 @@ export const buildRequests = function (validBidRequests, bidderRequest) { }; export const interpretResponse = function (serverResponse) { - const adServerPriceCurrency = config.getConfig('currency.adServerCurrency') || SUPPORTED_CURRENCY; const res = serverResponse.body; const bids = [] if (res) { res.seatbid.forEach(sb => { sb.bid.forEach(b => { - const adWithCorrectCurrency = b.adm - .replace(/\${AUCTION_PRICE_CURRENCY}/g, adServerPriceCurrency); bids.push({ requestId: b.impid, cpm: b.price, @@ -45,7 +57,7 @@ export const interpretResponse = function (serverResponse) { dealId: b.dealid, netRevenue: true, ttl: TIME_TO_LIVE_IN_SECONDS, - ad: adWithCorrectCurrency, + ad: b.adm, nurl: b.nurl, meta: { advertiserDomains: b.adomain @@ -58,13 +70,15 @@ export const interpretResponse = function (serverResponse) { }; export const onBidWon = function (bid) { - const cpm = bid.cpm || 0; - const cpmCurrency = bid.currency || SUPPORTED_CURRENCY; + // We intentionally use the price set by the publisher to replace the ${AUCTION_PRICE} macro + // instead of the `originalCpm` here. This notification is not used for billing, only for extra logging. + const publisherPrice = bid.cpm || 0; + const publisherCurrency = bid.currency || config.getConfig('currency.adServerCurrency') || SUPPORTED_CURRENCY; const adServerPrice = deepAccess(bid, 'adserverTargeting.hb_pb', 0); const adServerPriceCurrency = config.getConfig('currency.adServerCurrency') || SUPPORTED_CURRENCY; if (isStr(bid.nurl) && bid.nurl !== '') { - const winNotificationUrl = replaceAuctionPrice(bid.nurl, bid.originalCpm || cpm) - .replace(/\${AUCTION_PRICE_CURRENCY}/g, cpmCurrency) + const winNotificationUrl = replaceAuctionPrice(bid.nurl, publisherPrice) + .replace(/\${AUCTION_PRICE_CURRENCY}/g, publisherCurrency) .replace(/\${AD_SERVER_PRICE}/g, adServerPrice) .replace(/\${AD_SERVER_PRICE_CURRENCY}/g, adServerPriceCurrency); triggerPixel(winNotificationUrl); @@ -73,17 +87,13 @@ export const onBidWon = function (bid) { export const onTimeout = function (timeoutDataArray) { if (isArray(timeoutDataArray)) { - const refererInfo = getRefererInfo(); - const pageUrl = (refererInfo && refererInfo.referer) - ? refererInfo.referer - : window.location.href; + const pageUrl = getPageUrlFromRefererInfo(); timeoutDataArray.forEach(timeoutData => { const query = parseQueryStringParameters({ ad_unit_code: timeoutData.adUnitCode, auction_id: timeoutData.auctionId, bid_id: timeoutData.bidId, timeout: timeoutData.timeout, - placement_id: deepAccess(timeoutData, 'params.0.placementId'), page_url: pageUrl, }); const timeoutNotificationUrl = `${TIMEOUT_NOTIFICATION_ENDPOINT}?${query}`; @@ -92,13 +102,28 @@ export const onTimeout = function (timeoutDataArray) { } }; -function buildOpenRtbBidRequestPayload(validBidRequests, bidderRequest) { - const imps = validBidRequests.map(buildOpenRtbImpObject); - const timeout = bidderRequest.timeout || config.getConfig('bidderTimeout') || DEFAULT_TIMEOUT; - const pageUrl = (bidderRequest.refererInfo && bidderRequest.refererInfo.referer) +function getPageUrlFromRefererInfo() { + const refererInfo = getRefererInfo(); + return (refererInfo && refererInfo.referer) + ? refererInfo.referer + : window.location.href; +} + +function getPageUrlFromRequest(validBidRequest, bidderRequest) { + // pageUrl is considered only when testing to ensure that non-test requests always contain the correct URL + if (isTest(validBidRequest) && config.getConfig('pageUrl')) { + return config.getConfig('pageUrl'); + } + + return (bidderRequest.refererInfo && bidderRequest.refererInfo.referer) ? bidderRequest.refererInfo.referer : window.location.href; +} +function buildOpenRtbBidRequestPayload(validBidRequests, bidderRequest) { + const imps = validBidRequests.map(buildOpenRtbImpObject); + const timeout = bidderRequest.timeout || config.getConfig('bidderTimeout') || DEFAULT_TIMEOUT; + const pageUrl = getPageUrlFromRequest(validBidRequests[0], bidderRequest) const request = { id: bidderRequest.auctionId, at: 1, @@ -106,13 +131,12 @@ function buildOpenRtbBidRequestPayload(validBidRequests, bidderRequest) { cur: [SUPPORTED_CURRENCY], imp: imps, device: { - devicetype: getDevice(), - geo: getGeo(validBidRequests[0]) + devicetype: getDevice() }, site: { page: pageUrl, }, - test: getTest(validBidRequests[0]) + test: getTestAsNumber(validBidRequests[0]) }; return JSON.stringify(request); @@ -128,14 +152,8 @@ function buildOpenRtbImpObject(validBidRequest) { banner: { format: buildFormatArray(sizes), w: mainSize[0], - h: mainSize[1], - ext: { - kobler: { - pos: getPosition(validBidRequest) - } - } + h: mainSize[1] }, - tagid: validBidRequest.params.placementId, bidfloor: floorInfo.floor, bidfloorcur: floorInfo.currency, pmp: buildPmpObject(validBidRequest) @@ -157,17 +175,12 @@ function getDevice() { return 2; // personal computers } -function getGeo(validBidRequest) { - if (validBidRequest.params.zip) { - return { - zip: validBidRequest.params.zip - }; - } - return {}; +function getTestAsNumber(validBidRequest) { + return isTest(validBidRequest) ? 1 : 0; } -function getTest(validBidRequest) { - return validBidRequest.params.test ? 1 : 0; +function isTest(validBidRequest) { + return validBidRequest.params && validBidRequest.params.test === true; } function getSizes(validBidRequest) { @@ -188,10 +201,6 @@ function buildFormatArray(sizes) { }); } -function getPosition(validBidRequest) { - return parseInt(validBidRequest.params.position) || 0; -} - function getFloorInfo(validBidRequest, mainSize) { if (typeof validBidRequest.getFloor === 'function') { const sizeParam = mainSize[0] === 0 && mainSize[1] === 0 ? '*' : mainSize; @@ -209,11 +218,11 @@ function getFloorInfo(validBidRequest, mainSize) { } function getFloorPrice(validBidRequest) { - return parseFloat(validBidRequest.params.floorPrice) || 0.0; + return parseFloat(deepAccess(validBidRequest, 'params.floorPrice', 0.0)); } function buildPmpObject(validBidRequest) { - if (validBidRequest.params.dealIds) { + if (validBidRequest.params && validBidRequest.params.dealIds && isArray(validBidRequest.params.dealIds)) { return { deals: validBidRequest.params.dealIds.map(dealId => { return { diff --git a/modules/koblerBidAdapter.md b/modules/koblerBidAdapter.md index 7a7b2388367..63b0c3ead68 100644 --- a/modules/koblerBidAdapter.md +++ b/modules/koblerBidAdapter.md @@ -12,14 +12,15 @@ This adapter currently only supports Banner Ads. # Parameters -| Parameter (in `params`) | Scope | Type | Description | Example | -| --- | --- | --- | --- | --- | -| placementId | Required | String | The identifier of the placement, it has to be issued by Kobler. | `'xjer0ch8'` | -| zip | Optional | String | Zip code of the user or the medium. When multiple ad units are submitted together, it is enough to set this parameter on the first one. | `'102 22'` | -| test | Optional | Boolean | Whether the request is for testing only. When multiple ad units are submitted together, it is enough to set this parameter on the first one. Defaults to false. | `true` | -| floorPrice | Optional | Float | Floor price in CPM and USD. Can be used as an alternative to the [Floors module](https://docs.prebid.org/dev-docs/modules/floors.html), which is also supported by this adapter. Defaults to 0. | `5.0` | -| position | Optional | Integer | The position of the ad unit. Can be used to differentiate between ad units if the same placement ID is used across multiple ad units. The first ad unit should have a `position` of 0, the second one should have a `position` of 1 and so on. Defaults to 0. | `1` | -| dealIds | Optional | Array of Strings | Array of deal IDs. | `['abc328745', 'mxw243253']` | +| Parameter (in `params`) | Scope | Type | Description | Example | +|-------------------------|----------|------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------| +| test | Optional | Boolean | Whether the request is for testing only. When multiple ad units are submitted together, it is enough to set this parameter on the first one. Enables providing a custom URL through config.pageUrl. Defaults to false. | `true` | +| floorPrice | Optional | Float | Floor price in CPM and USD. Can be used as an alternative to the [Floors module](https://docs.prebid.org/dev-docs/modules/floors.html), which is also supported by this adapter. Defaults to 0. | `5.0` | +| dealIds | Optional | Array of Strings | Array of deal IDs. | `['abc328745', 'mxw243253']` | + +## Implicit parameters + +Kobler identifies the placement using the combination of the page URL and the allowed sizes. As a result, it's important that the correct sizes are provided in `banner.sizes` in order for Kobler to correctly identify the placement. The main, desired format should be the first element of this array. # Test Parameters ```javascript @@ -31,17 +32,14 @@ This adapter currently only supports Banner Ads. } }, bids: [{ - bidder: 'kobler', - params: { - placementId: 'k5H7et3R0' - } + bidder: 'kobler' }] }]; ``` In order to see a sample bid from Kobler (without a proper setup), you have to also do the following: -- Change the [`refererInfo` function](https://github.com/prebid/Prebid.js/blob/master/src/refererDetection.js) to return `'https://www.tv2.no/a/11734615'` as a [`referer`](https://github.com/prebid/Prebid.js/blob/caead3ccccc448e4cd09d074fd9f8833f56fe9b3/src/refererDetection.js#L169). This is necessary because Kobler only bids on recognized articles. -- Change the adapter's [`BIDDER_ENDPOINT`](https://github.com/prebid/Prebid.js/blob/master/modules/koblerBidAdapter.js#L8) to `'https://bid-service.dev.essrtb.com/bid/prebid_rtb_call'`. This endpoint belongs to the development server that is set up to always return a bid for the correct `placementId` and page URL combination. +- Set the `test` parameter to `true`. +- Set `config.pageUrl` to `'https://www.tv2.no/mening-og-analyse/14555348/'`. This is necessary because Kobler only bids on recognized articles. Kobler runs its own test campaign to make sure there is always a bid for this specific page URL. # Test Optional Parameters ```javascript @@ -55,11 +53,8 @@ In order to see a sample bid from Kobler (without a proper setup), you have to a bids: [{ bidder: 'kobler', params: { - placementId: 'k5H7et3R0', - zip: '102 22', test: true, floorPrice: 5.0, - position: 1, dealIds: ['abc328745', 'mxw243253'] } }] diff --git a/test/spec/modules/koblerBidAdapter_spec.js b/test/spec/modules/koblerBidAdapter_spec.js index 76c2c287989..8b45fead744 100644 --- a/test/spec/modules/koblerBidAdapter_spec.js +++ b/test/spec/modules/koblerBidAdapter_spec.js @@ -16,7 +16,7 @@ function createBidderRequest(auctionId, timeout, pageUrl) { } function createValidBidRequest(params, bidId, sizes) { - return { + const validBidRequest = { adUnitCode: 'adunit-code', bidId: bidId || '22c4871113f461', bidder: 'kobler', @@ -28,11 +28,12 @@ function createValidBidRequest(params, bidId, sizes) { sizes: sizes || [[300, 250], [320, 100]] } }, - params: params || { - placementId: 'tpw58278' - }, transactionTd: '04314114-15bd-4638-8664-bdb8bdc60bff' }; + if (params) { + validBidRequest.params = params; + } + return validBidRequest; } describe('KoblerAdapter', function () { @@ -56,7 +57,7 @@ describe('KoblerAdapter', function () { expect(result).to.be.false; }); - it('should not accept a request without params as valid', function () { + it('should not accept a request without mediaTypes and sizes as valid', function () { const bid = { bidId: 'e11768e8-3b71-4453-8698-0a2feb866589' }; @@ -66,11 +67,56 @@ describe('KoblerAdapter', function () { expect(result).to.be.false; }); - it('should not accept a request without placementId as valid', function () { + it('should not accept a request without mediaTypes and string sizes as valid', function () { const bid = { bidId: 'e11768e8-3b71-4453-8698-0a2feb866589', - params: { - someParam: 'abc' + sizes: 'string' + }; + + const result = spec.isBidRequestValid(bid); + + expect(result).to.be.false; + }); + + it('should not accept a request without mediaTypes and empty sizes as valid', function () { + const bid = { + bidId: 'e11768e8-3b71-4453-8698-0a2feb866589', + sizes: [] + }; + + const result = spec.isBidRequestValid(bid); + + expect(result).to.be.false; + }); + + it('should not accept a request without mediaTypes.banner and sizes as valid', function () { + const bid = { + bidId: 'e11768e8-3b71-4453-8698-0a2feb866589', + mediaTypes: {} + }; + + const result = spec.isBidRequestValid(bid); + + expect(result).to.be.false; + }); + + it('should not accept a request without mediaTypes.banner and empty sizes as valid', function () { + const bid = { + bidId: 'e11768e8-3b71-4453-8698-0a2feb866589', + sizes: [], + mediaTypes: {} + }; + + const result = spec.isBidRequestValid(bid); + + expect(result).to.be.false; + }); + + it('should not accept a request without sizes and string mediaTypes.banner as valid', function () { + const bid = { + bidId: 'e11768e8-3b71-4453-8698-0a2feb866589', + mediaTypes: { + banner: 'string' } }; @@ -79,12 +125,67 @@ describe('KoblerAdapter', function () { expect(result).to.be.false; }); - it('should accept a request with bidId and placementId as valid', function () { + it('should not accept a request without sizes and mediaTypes.banner.sizes as valid', function () { const bid = { bidId: 'e11768e8-3b71-4453-8698-0a2feb866589', - params: { - someParam: 'abc', - placementId: '8bde0923-1409-4253-9594-495b58d931ba' + mediaTypes: { + banner: {} + } + }; + + const result = spec.isBidRequestValid(bid); + + expect(result).to.be.false; + }); + + it('should not accept a request without sizes and string mediaTypes.banner.sizes as valid', function () { + const bid = { + bidId: 'e11768e8-3b71-4453-8698-0a2feb866589', + mediaTypes: { + banner: { + sizes: 'string' + } + } + }; + + const result = spec.isBidRequestValid(bid); + + expect(result).to.be.false; + }); + + it('should not accept a request without sizes and empty mediaTypes.banner.sizes as valid', function () { + const bid = { + bidId: 'e11768e8-3b71-4453-8698-0a2feb866589', + mediaTypes: { + banner: { + sizes: [] + } + } + }; + + const result = spec.isBidRequestValid(bid); + + expect(result).to.be.false; + }); + + it('should accept a request with bidId and sizes as valid', function () { + const bid = { + bidId: 'e11768e8-3b71-4453-8698-0a2feb866589', + sizes: [[5, 5]] + }; + + const result = spec.isBidRequestValid(bid); + + expect(result).to.be.true; + }); + + it('should accept a request with bidId and mediaTypes.banner.sizes as valid', function () { + const bid = { + bidId: 'e11768e8-3b71-4453-8698-0a2feb866589', + mediaTypes: { + banner: { + sizes: [[0, 0]] + } } }; @@ -114,13 +215,10 @@ describe('KoblerAdapter', function () { const firstSize = [400, 800]; const secondSize = [450, 950]; const sizes = [firstSize, secondSize]; - const placementId = 'tsjs86325'; const bidId = '3a56a019-4835-4f75-811c-76fac6853a2c'; const validBidRequests = [ createValidBidRequest( - { - placementId: placementId - }, + undefined, bidId, sizes ) @@ -132,7 +230,6 @@ describe('KoblerAdapter', function () { expect(openRtbRequest.imp.length).to.be.equal(1); expect(openRtbRequest.imp[0].id).to.be.equal(bidId); - expect(openRtbRequest.imp[0].tagid).to.be.equal(placementId); expect(openRtbRequest.imp[0].banner.w).to.be.equal(firstSize[0]); expect(openRtbRequest.imp[0].banner.h).to.be.equal(firstSize[1]); expect(openRtbRequest.imp[0].banner.format.length).to.be.equal(2); @@ -163,40 +260,31 @@ describe('KoblerAdapter', function () { expect(openRtbRequest.imp[0].banner.format[0].h).to.be.equal(0); }); - it('should use 0 as default position', function () { - const validBidRequests = [createValidBidRequest()]; - const bidderRequest = createBidderRequest(); - - const result = spec.buildRequests(validBidRequests, bidderRequest); - const openRtbRequest = JSON.parse(result.data); - - expect(openRtbRequest.imp.length).to.be.equal(1); - expect(openRtbRequest.imp[0].banner.ext.kobler.pos).to.be.equal(0); - }); - - it('should read zip from valid bid requests', function () { - const zip = '700 02'; + it('should read test from valid bid requests', function () { const validBidRequests = [ createValidBidRequest( { - placementId: 'nmah8324234', - zip: zip + test: true } ) ]; const bidderRequest = createBidderRequest(); const result = spec.buildRequests(validBidRequests, bidderRequest); - const openRtbRequest = JSON.parse(result.data); + expect(result.url).to.be.equal('https://bid-service.dev.essrtb.com/bid/prebid_rtb_call'); - expect(openRtbRequest.device.geo.zip).to.be.equal(zip); + const openRtbRequest = JSON.parse(result.data); + expect(openRtbRequest.site.page).to.be.equal('example.com'); + expect(openRtbRequest.test).to.be.equal(1); }); - it('should read test from valid bid requests', function () { + it('should read pageUrl from config when testing', function () { + config.setConfig({ + pageUrl: 'https://testing-url.com' + }); const validBidRequests = [ createValidBidRequest( { - placementId: 'zwop842799', test: true } ) @@ -204,49 +292,40 @@ describe('KoblerAdapter', function () { const bidderRequest = createBidderRequest(); const result = spec.buildRequests(validBidRequests, bidderRequest); - const openRtbRequest = JSON.parse(result.data); + expect(result.url).to.be.equal('https://bid-service.dev.essrtb.com/bid/prebid_rtb_call'); + const openRtbRequest = JSON.parse(result.data); + expect(openRtbRequest.site.page).to.be.equal('https://testing-url.com'); expect(openRtbRequest.test).to.be.equal(1); }); - it('should read floorPrice from valid bid requests', function () { - const floorPrice = 4.343; + it('should not read pageUrl from config when not testing', function () { + config.setConfig({ + pageUrl: 'https://testing-url.com' + }); const validBidRequests = [ - createValidBidRequest( - { - placementId: 'oqr3224234', - floorPrice: floorPrice - } - ) + createValidBidRequest() ]; - const bidderRequest = createBidderRequest(); + const bidderRequest = createBidderRequest( + 'f85d61cc-ed11-4b6c-aefb-87943263cedb', + 2000, + 'https://non-testing-url.net' + ); const result = spec.buildRequests(validBidRequests, bidderRequest); - const openRtbRequest = JSON.parse(result.data); + expect(result.url).to.be.equal('https://bid.essrtb.com/bid/prebid_rtb_call'); - expect(openRtbRequest.imp.length).to.be.equal(1); - expect(openRtbRequest.imp[0].bidfloor).to.be.equal(floorPrice); + const openRtbRequest = JSON.parse(result.data); + expect(openRtbRequest.site.page).to.be.equal('https://non-testing-url.net'); + expect(openRtbRequest.test).to.be.equal(0); }); - it('should read position from valid bid requests', function () { - const placementId = 'yzksf234592'; + it('should read floorPrice from valid bid requests', function () { + const floorPrice = 4.343; const validBidRequests = [ createValidBidRequest( { - placementId: placementId, - position: 1 - } - ), - createValidBidRequest( - { - placementId: placementId, - position: 2 - } - ), - createValidBidRequest( - { - placementId: placementId, - position: 3 + floorPrice: floorPrice } ) ]; @@ -255,13 +334,8 @@ describe('KoblerAdapter', function () { const result = spec.buildRequests(validBidRequests, bidderRequest); const openRtbRequest = JSON.parse(result.data); - expect(openRtbRequest.imp.length).to.be.equal(3); - expect(openRtbRequest.imp[0].banner.ext.kobler.pos).to.be.equal(1); - expect(openRtbRequest.imp[0].tagid).to.be.equal(placementId); - expect(openRtbRequest.imp[1].banner.ext.kobler.pos).to.be.equal(2); - expect(openRtbRequest.imp[1].tagid).to.be.equal(placementId); - expect(openRtbRequest.imp[2].banner.ext.kobler.pos).to.be.equal(3); - expect(openRtbRequest.imp[2].tagid).to.be.equal(placementId); + expect(openRtbRequest.imp.length).to.be.equal(1); + expect(openRtbRequest.imp[0].bidfloor).to.be.equal(floorPrice); }); it('should read dealIds from valid bid requests', function () { @@ -270,13 +344,11 @@ describe('KoblerAdapter', function () { const validBidRequests = [ createValidBidRequest( { - placementId: 'rsl1239823', dealIds: dealIds1 } ), createValidBidRequest( { - placementId: 'pqw234232', dealIds: dealIds2 } ) @@ -353,10 +425,7 @@ describe('KoblerAdapter', function () { const validBidRequests = [ createValidBidRequest( { - placementId: 'pcha322364', - zip: '0015', floorPrice: 5.6234, - position: 1, dealIds: ['623472534328234'] }, '953ee65d-d18a-484f-a840-d3056185a060', @@ -364,19 +433,14 @@ describe('KoblerAdapter', function () { ), createValidBidRequest( { - placementId: 'sdfgoi32y4', floorPrice: 3.2543, - position: 2, dealIds: ['92368234753283', '263845832942'] }, '8320bf79-9d90-4a17-87c6-5d505706a921', [[400, 500], [200, 250], [300, 350]] ), createValidBidRequest( - { - placementId: 'gwms2738647', - position: 3 - }, + undefined, 'd0de713b-32e3-4191-a2df-a007f08ffe72', [[800, 900]] ) @@ -406,14 +470,8 @@ describe('KoblerAdapter', function () { } ], w: 400, - h: 600, - ext: { - kobler: { - pos: 1 - } - } + h: 600 }, - tagid: 'pcha322364', bidfloor: 5.6234, bidfloorcur: 'USD', pmp: { @@ -442,14 +500,8 @@ describe('KoblerAdapter', function () { } ], w: 400, - h: 500, - ext: { - kobler: { - pos: 2 - } - } + h: 500 }, - tagid: 'sdfgoi32y4', bidfloor: 3.2543, bidfloorcur: 'USD', pmp: { @@ -473,24 +525,15 @@ describe('KoblerAdapter', function () { } ], w: 800, - h: 900, - ext: { - kobler: { - pos: 3 - } - } + h: 900 }, - tagid: 'gwms2738647', bidfloor: 0, bidfloorcur: 'USD', pmp: {} } ], device: { - devicetype: 2, - geo: { - zip: '0015' - } + devicetype: 2 }, site: { page: 'bid.kobler.no' @@ -531,7 +574,7 @@ describe('KoblerAdapter', function () { dealid: '', w: 320, h: 250, - adm: '', + adm: '', adomain: [ 'https://kobler.no' ] @@ -544,7 +587,7 @@ describe('KoblerAdapter', function () { dealid: '2783483223432342', w: 580, h: 400, - adm: '', + adm: '', adomain: [ 'https://bid.kobler.no' ] @@ -568,7 +611,7 @@ describe('KoblerAdapter', function () { dealId: '', netRevenue: true, ttl: 600, - ad: '', + ad: '', nurl: 'https://atag.essrtb.com/serve/prebid_win_notification?payload=sdhfusdaobfadslf234324&sp=${AUCTION_PRICE}&sp_cur=${AUCTION_PRICE_CURRENCY}&asp=${AD_SERVER_PRICE}&asp_cur=${AD_SERVER_PRICE_CURRENCY}', meta: { advertiserDomains: [ @@ -586,7 +629,7 @@ describe('KoblerAdapter', function () { dealId: '2783483223432342', netRevenue: true, ttl: 600, - ad: '', + ad: '', nurl: 'https://atag.essrtb.com/serve/prebid_win_notification?payload=nbashgufvishdafjk23432&sp=${AUCTION_PRICE}&sp_cur=${AUCTION_PRICE_CURRENCY}&asp=${AD_SERVER_PRICE}&asp_cur=${AD_SERVER_PRICE_CURRENCY}', meta: { advertiserDomains: [ @@ -628,6 +671,7 @@ describe('KoblerAdapter', function () { } }); spec.onBidWon({ + originalCpm: 1.532, cpm: 8.341, currency: 'NOK', nurl: 'https://atag.essrtb.com/serve/prebid_win_notification?payload=sdhfusdaobfadslf234324&sp=${AUCTION_PRICE}&sp_cur=${AUCTION_PRICE_CURRENCY}&asp=${AD_SERVER_PRICE}&asp_cur=${AD_SERVER_PRICE_CURRENCY}', @@ -670,22 +714,14 @@ describe('KoblerAdapter', function () { auctionId: 'a1fba829-dd41-409f-acfb-b7b0ac5f30c6', bidId: 'ef236c6c-e934-406b-a877-d7be8e8a839a', timeout: 100, - params: [ - { - placementId: 'xrwg62731', - } - ], + params: [], }, { adUnitCode: 'adunit-code-2', auctionId: 'a1fba829-dd41-409f-acfb-b7b0ac5f30c6', bidId: 'ca4121c8-9a4a-46ba-a624-e9b64af206f2', timeout: 100, - params: [ - { - placementId: 'bc482234', - } - ], + params: [], } ]); @@ -693,12 +729,12 @@ describe('KoblerAdapter', function () { expect(utils.triggerPixel.getCall(0).args[0]).to.be.equal( 'https://bid.essrtb.com/notify/prebid_timeout?ad_unit_code=adunit-code&' + 'auction_id=a1fba829-dd41-409f-acfb-b7b0ac5f30c6&bid_id=ef236c6c-e934-406b-a877-d7be8e8a839a&timeout=100&' + - 'placement_id=xrwg62731&page_url=' + encodeURIComponent(getRefererInfo().referer) + 'page_url=' + encodeURIComponent(getRefererInfo().referer) ); expect(utils.triggerPixel.getCall(1).args[0]).to.be.equal( 'https://bid.essrtb.com/notify/prebid_timeout?ad_unit_code=adunit-code-2&' + 'auction_id=a1fba829-dd41-409f-acfb-b7b0ac5f30c6&bid_id=ca4121c8-9a4a-46ba-a624-e9b64af206f2&timeout=100&' + - 'placement_id=bc482234&page_url=' + encodeURIComponent(getRefererInfo().referer) + 'page_url=' + encodeURIComponent(getRefererInfo().referer) ); }); }); From b898bba092fe70bd4eac397f1b2ec149991641e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Ven=C3=A2ncio?= <45434454+jvnnc@users.noreply.github.com> Date: Thu, 26 May 2022 12:38:19 -0300 Subject: [PATCH 47/76] NaveggId module: fixed regex used to get naveggId from LocalStorage (#8441) * fixed regex used to get naveggId from LocalStorage * added unit tests Co-authored-by: Jose --- modules/naveggIdSystem.js | 5 +++-- test/spec/modules/naveggIdSystem_spec.js | 26 +++++++++++++++++++++++- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/modules/naveggIdSystem.js b/modules/naveggIdSystem.js index 7bd86879e9d..d7f5f712252 100644 --- a/modules/naveggIdSystem.js +++ b/modules/naveggIdSystem.js @@ -36,10 +36,11 @@ function readnavIDFromCookie() { function readnvgnavFromLocalStorage() { var i; - const query = '^nvg|^nav'; + const query = /[nvga]{3}\d+/; for (i in window.localStorage) { if (i.match(query) || (!query && typeof i === 'string')) { - return storage.getDataFromLocalStorage(i.match(query).input); + const naveggId = storage.getDataFromLocalStorage(i.match(query).input).split('|')[0] + return naveggId.split('_')[0]; } } } diff --git a/test/spec/modules/naveggIdSystem_spec.js b/test/spec/modules/naveggIdSystem_spec.js index c0973a05372..2c4f1cda859 100644 --- a/test/spec/modules/naveggIdSystem_spec.js +++ b/test/spec/modules/naveggIdSystem_spec.js @@ -1,6 +1,15 @@ import { naveggIdSubmodule, storage } from 'modules/naveggIdSystem.js'; describe('naveggId', function () { + let sandbox; + beforeEach(() => { + sandbox = sinon.sandbox.create(); + sandbox.stub(storage, 'getDataFromLocalStorage'); + }); + afterEach(() => { + sandbox.restore(); + }); + it('should NOT find navegg id', function () { let id = naveggIdSubmodule.getId(); @@ -14,8 +23,23 @@ describe('naveggId', function () { }) it('getId() should return "test-nvggid" id from local storage NAVEGG_ID', function() { - sinon.stub(storage, 'getDataFromLocalStorage').withArgs('nvggid').returns('test-ninvggidd'); + storage.getDataFromLocalStorage.callsFake(() => 'test-ninvggidd') + let id = naveggIdSubmodule.getId(); expect(id).to.be.deep.equal({id: 'test-ninvggidd'}) }) + + it('getId() should return "test-nvggid" id from local storage NAV0', function() { + storage.getDataFromLocalStorage.callsFake(() => 'nvgid-nav0') + + let id = naveggIdSubmodule.getId(); + expect(id).to.be.deep.equal({id: 'nvgid-nav0'}) + }) + + it('getId() should return "test-nvggid" id from local storage NVG0', function() { + storage.getDataFromLocalStorage.callsFake(() => 'nvgid-nvg0') + + let id = naveggIdSubmodule.getId(); + expect(id).to.be.deep.equal({id: 'nvgid-nvg0'}) + }) }); From 599c8275c0373e35ae15104de8d52023d6003406 Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 26 May 2022 18:46:16 +0300 Subject: [PATCH 48/76] support storageAllowed restriction on unit tests for prebid 7 --- test/spec/modules/taboolaBidAdapter_spec.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index 30553bd9190..1178d4062c4 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -12,6 +12,12 @@ describe('Taboola Adapter', function () { getCookie = sinon.stub(userData.storageManager, 'getCookie'); getDataFromLocalStorage = sinon.stub(userData.storageManager, 'getDataFromLocalStorage'); localStorageIsEnabled = sinon.stub(userData.storageManager, 'localStorageIsEnabled'); + + $$PREBID_GLOBAL$$.bidderSettings = { + taboola: { + storageAllowed: true + } + }; }); afterEach(() => { From 37e9f2bd89f722b3788cc183c75f226f9e3945c9 Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 7 Feb 2022 12:18:28 +0200 Subject: [PATCH 49/76] create taboola adapter --- modules/taboolaBidAdapter.js | 153 +++++++++++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 modules/taboolaBidAdapter.js diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js new file mode 100644 index 00000000000..c9019a8e1e4 --- /dev/null +++ b/modules/taboolaBidAdapter.js @@ -0,0 +1,153 @@ +// jshint esversion: 6, es3: false, node: true +'use strict'; + +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'taboola'; +const GVLID = 42; +const CURRENCY = 'USD'; +const END_POINT_URL = 'http://taboolahb.bidder.taboolasyndication.com' + +export const spec = { + supportedMediaTypes: [BANNER], + gvlid: GVLID, + code: BIDDER_CODE, + isBidRequestValid: (bidRequest) => { + return !!(bidRequest.sizes && + bidRequest.params && + bidRequest.params.publisherId && + bidRequest.params.tagId); + }, + getUserSyncs: (syncOptions, responses, gdprConsent, uspConsent) => { + return []; + }, + buildRequests: (validBidRequests, bidderRequest) => { + const [bidRequest] = validBidRequests; + const {bcat = [], badv = [], publisherId} = bidRequest.params; + const site = getSiteProperties(bidRequest.params, bidderRequest.refererInfo.referer); + const device = {ua: navigator.userAgent}; + const timeout = bidderRequest.timeout; + const imps = getImps(validBidRequests); + + const request = { + id: bidderRequest.auctionId, + imp: imps, + site, + device, + source: {fd: 1}, + tmax: timeout, + bcat: bcat, + badv: badv + }; + const url = [END_POINT_URL, publisherId].join('?p='); + + return { + url, + method: 'POST', + data: JSON.stringify(request), + bids: validBidRequests + }; + }, + interpretResponse: (serverResponse, {bids}) => { + if (!bids) { + return []; + } + + const {bidResponses, cur: currency} = getBidResponses(serverResponse); + + if (!bidResponses) { + return []; + } + + return bids.map((bid, id) => { + const bidResponse = bidResponses[id]; + if (!bidResponse) { + return; + } + + const {price: cpm, crid: creativeId, adm: ad, w: width, h: height, adomain: advertiserDomains, meta = {} + } = bidResponse; + + if (advertiserDomains && advertiserDomains.length > 0) { + meta.advertiserDomains = advertiserDomains + } + + return { + requestId: bid.bidId, + ttl: 360, + mediaType: BANNER, + cpm, + creativeId, + currency, + ad, + width, + height, + meta, + netRevenue: false + }; + }).filter(Boolean); + }, +}; + +registerBidder(spec); + +function getSiteProperties({publisherId, bcat = []}, page) { + return { + id: publisherId, + name: publisherId, + domain: window.location.host, + page, + cat: bcat, + publisher: { + id: publisherId + }, + content: { + language: navigator.language + } + } +} + +function getImps(validBidRequests) { + return validBidRequests.map((bid, id) => { + const {tagId, bidfloor = null, bidfloorcur = CURRENCY} = bid.params; + + return { + id: id + 1, + banner: getBanners(bid), + tagid: tagId, + bidfloor, + bidfloorcur, + }; + }); +} + +function getBanners(bid) { + return getSizes(bid.sizes); +} + +function getSizes(sizes) { + return sizes.map(size => { + return { + h: size[0], + w: size[1] + } + }) +} + +function getBidResponses({body}) { + if (!body || (body && !body.bidResponse)) { + return []; + } + + const {seatbid, cur} = body.bidResponse; + + if (!seatbid.length && !seatbid[0] && seatbid[0].bids) { + return []; + } + + return { + bidResponses: seatbid[0].bid, + cur + }; +} From 9e518ba1a73b75a1e4b6e9022833f439a07f059b Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 7 Feb 2022 12:35:51 +0200 Subject: [PATCH 50/76] create taboola adapter md --- modules/taboolaBidAdapter.md | 43 ++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 modules/taboolaBidAdapter.md diff --git a/modules/taboolaBidAdapter.md b/modules/taboolaBidAdapter.md new file mode 100644 index 00000000000..194f5bec7fe --- /dev/null +++ b/modules/taboolaBidAdapter.md @@ -0,0 +1,43 @@ +# Overview + +``` +Module Name: Taboola Adapter +Module Type: Bidder Adapter +Maintainer: someone@taboola.com //todo: need to fill this Maintainer email. +``` + +# Description + +Module that connects to Taboola bidder to fetch bids. +support display format. Using OpenRTB standard. + +# Configuration + +## Bidder and usersync URLs + +The Outbrain adapter does not work without setting the correct bidder and usersync URLs. +You will receive the URLs when contacting us. + +# Test Display Parameters +``` + var adUnits = [{ + code: 'your-unit-container-id', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]], + } + }, + bids: [{ + bidder: 'taboola', + params: { + tagId: 'test-1', + publisherId: 'test', + bidfloor: 0.25, // optional default is null + bidfloorcur: 'USD', // optional default is USD + bcat: ['IAB1-1'], // optional default is [] + badv: ['example.com'] // optional default is [] + } + }] + }]; + +``` From 3e2a910955c7c11ebd45d563331f27a47688504f Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 7 Feb 2022 17:38:50 +0200 Subject: [PATCH 51/76] taboolaBidAdapter.js - small fixes taboolaBidAdapter_spec.js - new UT --- modules/taboolaBidAdapter.js | 2 +- test/spec/modules/taboolaBidAdapter_spec.js | 249 ++++++++++++++++++++ 2 files changed, 250 insertions(+), 1 deletion(-) create mode 100644 test/spec/modules/taboolaBidAdapter_spec.js diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js index c9019a8e1e4..3d57d8c9b82 100644 --- a/modules/taboolaBidAdapter.js +++ b/modules/taboolaBidAdapter.js @@ -142,7 +142,7 @@ function getBidResponses({body}) { const {seatbid, cur} = body.bidResponse; - if (!seatbid.length && !seatbid[0] && seatbid[0].bids) { + if (!seatbid.length || !seatbid[0].bid) { return []; } diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js new file mode 100644 index 00000000000..13572737211 --- /dev/null +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -0,0 +1,249 @@ +import {expect} from 'chai'; +import {spec} from 'modules/taboolaBidAdapter.js'; + +describe.only('Taboola Adapter', function () { + const commonBidRequest = { + bidder: 'taboola', + params: { + publisherId: 'publisherId', + tagId: 'placement name' + }, + bidId: 'aa43860a-4644-442a-b5e0-93f268cs4d19', + auctionId: '65746dca-26f3-4186-be13-dfa63469b1b7', + } + + const displayBidRequestParams = { + sizes: [ + [300, 250] + ] + } + + describe('isBidRequestValid', function () { + it('should fail when bid is invalid - tagId isn`t defined', function () { + const bid = { + bidder: 'taboola', + params: { + publisherId: 'publisherId' + }, + ...displayBidRequestParams + } + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) + + it('should fail when bid is invalid - publisherId isn`t defined', function () { + const bid = { + bidder: 'taboola', + params: { + tagId: 'below the article' + }, + ...displayBidRequestParams + } + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) + + it('should fail when bid is invalid - sizes isn`t defined', function () { + const bid = { + bidder: 'taboola', + params: { + publisherId: 'publisherId', + tagId: 'below the article' + }, + } + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) + + it('should succeed when bid contains valid', function () { + const bid = { + bidder: 'taboola', + params: { + publisherId: 'publisherId', + tagId: 'below the article' + }, + ...displayBidRequestParams, + } + expect(spec.isBidRequestValid(bid)).to.equal(true) + }) + }) + + describe('buildRequests', function () { + const commonBidderRequest = { + refererInfo: { + referer: 'https://example.com/' + } + } + + it('should build display request', function () { + const bidRequest = { + ...commonBidRequest, + ...displayBidRequestParams, + } + const expectedData = { + 'imp': [{ + 'id': 1, + 'banner': [{'h': displayBidRequestParams.sizes[0][0], 'w': displayBidRequestParams.sizes[0][1]}], + 'tagid': 'placement name', + 'bidfloor': null, + 'bidfloorcur': 'USD' + }], + 'site': { + 'id': 'publisherId', + 'name': 'publisherId', + 'domain': window.location.host, + 'page': 'https://example.com/', + 'cat': [], + 'publisher': {'id': 'publisherId'}, + 'content': {'language': 'en-US'} + }, + 'device': {'ua': navigator.userAgent}, + 'source': {'fd': 1}, + 'bcat': [], + 'badv': [] + }; + + const res = spec.buildRequests([bidRequest], commonBidderRequest) + + expect(res.url).to.equal('http://taboolahb.bidder.taboolasyndication.com?p=publisherId') + expect(res.data).to.deep.equal(JSON.stringify(expectedData)) + }) + + it('should pass optional parameters in request', function () { + const optionalParams = { + badv: ['adadadbcd.com'], + bcat: ['IAB25', 'IAB7-39'], + bidfloor: 0.25, + bidfloorcur: 'EUR' + } + const bidRequest = { + ...commonBidRequest, + ...displayBidRequestParams, + params: {...commonBidRequest.params, ...optionalParams} + } + + const res = spec.buildRequests([bidRequest], commonBidderRequest) + const resData = JSON.parse(res.data) + expect(resData.bcat).to.deep.equal(['IAB25', 'IAB7-39']) + expect(resData.badv).to.deep.equal(['adadadbcd.com']) + expect(resData.imp[0].bidfloor).to.deep.equal(0.25) + expect(resData.imp[0].bidfloorcur).to.deep.equal('EUR') + }); + + it('should pass bidder timeout', function () { + const bidRequest = { + ...commonBidRequest, + ...displayBidRequestParams, + } + + const bidderRequest = { + ...commonBidderRequest, + timeout: 500 + } + const res = spec.buildRequests([bidRequest], bidderRequest) + const resData = JSON.parse(res.data) + expect(resData.tmax).to.equal(500) + }); + }) + + describe('interpretResponse', function () { + const serverResponse = { + body: { + 'bidResponse': { + 'id': '49ffg4d58ef9a163a69fhgfghd4fad03621b9e036f24f7_15', + 'seatbid': [ + { + 'bid': [ + { + 'id': '0b3dd94348-134b-435f-8db5-6bf5afgfc39e86c', + 'impid': '1', + 'price': 0.342068, + 'adid': '2785119545551083381', + 'adm': '\u003chtml\u003e\n\u003chead\u003e\n\u003cmeta charset\u003d"UTF-8"\u003e\n\u003cmeta http-equiv\u003d"Content-Type" content\u003d"text/html; charset\u003dutf-8"/\u003e\u003c/head\u003e\n\u003cbody style\u003d"margin: 0px; overflow:hidden;"\u003e \n\u003cscript type\u003d"text/javascript"\u003e\nwindow.tbl_trc_domain \u003d \u0027us-trc.taboola.com\u0027;\nwindow._taboola \u003d window._taboola || [];\n_taboola.push({article:\u0027auto\u0027});\n!function (e, f, u, i) {\nif (!document.getElementById(i)){\ne.async \u003d 1;\ne.src \u003d u;\ne.id \u003d i;\nf.parentNode.insertBefore(e, f);\n}\n}(document.createElement(\u0027script\u0027),\ndocument.getElementsByTagName(\u0027script\u0027)[0],\n\u0027//cdn.taboola.com/libtrc/wattpad-placement-255/loader.js\u0027,\n\u0027tb_loader_script\u0027);\nif(window.performance \u0026\u0026 typeof window.performance.mark \u003d\u003d \u0027function\u0027)\n{window.performance.mark(\u0027tbl_ic\u0027);}\n\u003c/script\u003e\n\n\u003cdiv id\u003d"taboola-below-article-thumbnails" style\u003d"height: 250px; width: 300px;"\u003e\u003c/div\u003e\n\u003cscript type\u003d"text/javascript"\u003e\nwindow._taboola \u003d window._taboola || [];\n_taboola.push({\nmode: \u0027Rbox_300x250_1x1\u0027,\ncontainer: \u0027taboola-below-article-thumbnails\u0027,\nplacement: \u0027wattpad.com_P18694_S257846_W300_H250_N1_TB\u0027,\ntarget_type: \u0027mix\u0027,\n"rtb-win":{ \nbi:\u002749ff4d58ef9a163a696d4fad03621b9e036f24f7_15\u0027,\ncu:\u0027USD\u0027,\nwp:\u0027${AUCTION_PRICE:BF}\u0027,\nwcb:\u0027~!audex-display-impression!~\u0027,\nrt:\u00271643227025284\u0027,\nrdc:\u0027us.taboolasyndication.com\u0027,\nti:\u00274212\u0027,\nex:\u0027MagniteSCoD\u0027,\nbs:\u0027xapi:257846:lvvSm6Ak7_wE\u0027,\nbp:\u002718694\u0027,\nbd:\u0027wattpad.com\u0027,\nsi:\u00279964\u0027\n} \n,\nrec: {"trc":{"si":"a69c7df43b2334f0aa337c37e2d80c21","sd":"v2_a69c7df43b2334f0aa337c37e2d80c21_3c70f7c7d64a65b15e4a4175c9a2cfa51072f04bMagniteSCoD_1643227025_1643227025_CJS1tQEQ5NdWGPLA0d76xo-9ngEgASgEMCY4iegHQIroB0iB09kDUKPPB1gAYABop-G2i_Hl-eVucAA","ui":"3c70f7c7d64a65b15e4a4175c9a2cfa51072f04bMagniteSCoD","plc":"PHON","wi":"-643136642229425433","cc":"CA","route":"US:US:V","el2r":["bulk-metrics","debug","social","metrics","perf"],"uvpw":"1","pi":"1420260","cpb":"GNO629MGIJz__________wEqGXVzLnRhYm9vbGFzeW5kaWNhdGlvbi5jb20yC3RyYy1zY29kMTI5OIDwmrUMQInoB0iK6AdQgdPZA1ijzwdjCN3__________wEQ3f__________ARgjZGMI3AoQoBAYFmRjCNIDEOAGGAhkYwiWFBCcHBgYZGMI9AUQiwoYC2RjCNkUEPkcGB1kYwj0FBCeHRgfZGorNDlmZjRkNThlZjlhMTYzYTY5NmQ0ZmFkMDM2MjFiOWUwMzZmMjRmN18xNXgCgAHpbIgBrPvTxQE","dcga":{"pubConfigOverride":{"border-color":"black","font-weight":"bold","inherit-title-color":"true","module-name":"cta-lazy-module","enable-call-to-action-creative-component":"true","disable-cta-on-custom-module":"true"}},"tslt":{"p-video-overlay":{"cancel":"סגור","goto":"עבור לדף"},"read-more":{"DEFAULT_CAPTION":"%D7%A7%D7%A8%D7%90%20%D7%A2%D7%95%D7%93"},"next-up":{"BTN_TEXT":"לקריאת התוכן הבא"},"time-ago":{"now":"עכשיו","today":"היום","yesterday":"אתמול","minutes":"לפני {0} דקות","hour":"לפני שעה","hours":"לפני {0} שעות","days":"לפני {0} ימים"},"explore-more":{"TITLE_TEXT":"המשיכו לקרוא","POPUP_TEXT":"אל תפספסו הזדמנות לקרוא עוד תוכן מעולה, רגע לפני שתעזבו"}},"evh":"-1964913910","vl":[{"ri":"185db6d274ce94b27caaabd9eed7915b","uip":"wattpad.com_P18694_S257846_W300_H250_N1_TB","ppb":"COIF","estimation_method":"EcpmEstimationMethodType_ESTIMATION","baseline_variant":"false","original_ecpm":"0.4750949889421463","v":[{"thumbnail":"https://cdn.taboola.com/libtrc/static/thumbnails/a2b272be514ca3ebe3f97a4a32a41db5.jpg","all-thumbnails":"https://cdn.taboola.com/libtrc/static/thumbnails/a2b272be514ca3ebe3f97a4a32a41db5.jpg!-#@1600x1000","origin":"default","thumb-size":"1600x1000","title":"Get Roofing Services At Prices You Can Afford In Edmonton","type":"text","published-date":"1641997069","branding-text":"Roofing Services | Search Ads","url":"https://inneth-conded.xyz/9ad2e613-8777-4fe7-9a52-386c88879289?site\u003dwattpad-placement-255\u0026site_id\u003d1420260\u0026title\u003dGet+Roofing+Services+At+Prices+You+Can+Afford+In+Edmonton\u0026platform\u003dSmartphone\u0026campaign_id\u003d15573949\u0026campaign_item_id\u003d3108610633\u0026thumbnail\u003dhttp%3A%2F%2Fcdn.taboola.com%2Flibtrc%2Fstatic%2Fthumbnails%2Fa2b272be514ca3ebe3f97a4a32a41db5.jpg\u0026cpc\u003d{cpc}\u0026click_id\u003dGiCIypnAQogsMTFL3e_mPaVM2qLvK3KRU6LWzEMUgeB6piCit1Uox6CNr5v5n-x1\u0026tblci\u003dGiCIypnAQogsMTFL3e_mPaVM2qLvK3KRU6LWzEMUgeB6piCit1Uox6CNr5v5n-x1#tblciGiCIypnAQogsMTFL3e_mPaVM2qLvK3KRU6LWzEMUgeB6piCit1Uox6CNr5v5n-x1","duration":"0","sig":"328243c4127ff16e3fdcd7270bab908f6f3fc5b4c98d","item-id":"~~V1~~2785119550041083381~~PnBkfBE9JnQxpahv0adkcuIcmMhroRAHXwLZd-7zhunTxvAnL2wqac4MyzR7uD46gj3kUkbS3FhelBtnsiJV6MhkDZRZzzIqDobN6rWmCPA3hYz5D3PLat6nhIftiT1lwdxwdlxkeV_Mfb3eos_TQavImGhxk0e7psNAZxHJ9RKL2w3lppALGgQJoy2o6lkf-pOqODtX1VkgWpEEM4WsVoWOnUTAwdyGd-8yrze8CWNp752y28hl7lleicyO1vByRdbgwlJdnqyroTPEQNNEn1JRxBOSYSWt-Xm3vkPm-G4","uploader":"","is-syndicated":"true","publisher":"search","id":"~~V1~~2785119550041083381~~PnBkfBE9JnQxpahv0adkcuIcmMhroRAHXwLZd-7zhunTxvAnL2wqac4MyzR7uD46gj3kUkbS3FhelBtnsiJV6MhkDZRZzzIqDobN6rWmCPA3hYz5D3PLat6nhIftiT1lwdxwdlxkeV_Mfb3eos_TQavImGhxk0e7psNAZxHJ9RKL2w3lppALGgQJoy2o6lkf-pOqODtX1VkgWpEEM4WsVoWOnUTAwdyGd-8yrze8CWNp752y28hl7lleicyO1vByRdbgwlJdnqyroTPEQNNEn1JRxBOSYSWt-Xm3vkPm-G4","category":"home","views":"0","itp":[{"u":"https://trc.taboola.com/1326786/log/3/unip?en\u003dclickersusa","t":"c"}],"description":""}]}],"cpcud":{"upc":"0.0","upr":"0.0"}}}\n});\n\u003c/script\u003e\n\n\u003cscript type\u003d"text/javascript"\u003e\nwindow._taboola \u003d window._taboola || [];\n_taboola.push({flush: true});\n\u003c/script\u003e\n\n\u003c/body\u003e\n\u003c/html\u003e', + 'adomain': [ + 'example.xyz' + ], + 'cid': '15744349', + 'crid': '278195503434041083381', + 'w': 300, + 'h': 250, + 'lurl': 'http://us-trc.taboola.com/sample' + } + ], + 'seat': '14204545260' + } + ], + 'bidid': 'da43860a-4644-442a-b5e0-93f268cf8d19', + 'cur': 'USD' + }, + 'debugResponse': {} + } + }; + + const request = { + bids: [ + { + ...commonBidRequest, + ...displayBidRequestParams + } + ] + } + + it('should return empty array if no valid bids', function () { + const res = spec.interpretResponse(serverResponse, []) + expect(res).to.be.an('array').that.is.empty + }); + + it('should return empty array if no server response', function () { + const res = spec.interpretResponse({}, request) + expect(res).to.be.an('array').that.is.empty + }); + + it('should return empty array if server response without seatbid', function () { + const overriddenServerResponse = {...serverResponse}; + const seatbid = {...serverResponse.body.bidResponse.seatbid[0]}; + overriddenServerResponse.body.bidResponse.seatbid[0] = {}; + + const res = spec.interpretResponse(overriddenServerResponse, request) + expect(res).to.be.an('array').that.is.empty + + overriddenServerResponse.body.bidResponse.seatbid[0] = seatbid; + }); + + it('should return empty array if server response without bids', function () { + const overriddenServerResponse = {...serverResponse}; + const bid = [...serverResponse.body.bidResponse.seatbid[0].bid]; + overriddenServerResponse.body.bidResponse.seatbid[0].bid = {}; + + const res = spec.interpretResponse(overriddenServerResponse, request) + expect(res).to.be.an('array').that.is.empty + + overriddenServerResponse.body.bidResponse.seatbid[0].bid = bid; + }); + + it('should interpret display response', function () { + const [bid] = serverResponse.body.bidResponse.seatbid[0].bid; + const expectedRes = [ + { + requestId: request.bids[0].bidId, + cpm: bid.price, + creativeId: bid.crid, + ttl: 360, + netRevenue: false, + currency: serverResponse.body.bidResponse.cur, + mediaType: 'banner', + ad: bid.adm, + width: bid.w, + height: bid.h, + meta: { + 'advertiserDomains': bid.adomain + }, + } + ] + + const res = spec.interpretResponse(serverResponse, request) + expect(res).to.deep.equal(expectedRes) + }); + }) + + describe('getUserSyncs', function () { + // todo: add UT for getUserSyncs + }) +}) From 2aa70e401c417756c812a3476ef72b263b7becd3 Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 7 Feb 2022 18:09:36 +0200 Subject: [PATCH 52/76] taboolaBidAdapter.js - small fixes taboolaBidAdapter_spec.js - new UT --- modules/taboolaBidAdapter.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/taboolaBidAdapter.md b/modules/taboolaBidAdapter.md index 194f5bec7fe..554eb4de442 100644 --- a/modules/taboolaBidAdapter.md +++ b/modules/taboolaBidAdapter.md @@ -3,7 +3,7 @@ ``` Module Name: Taboola Adapter Module Type: Bidder Adapter -Maintainer: someone@taboola.com //todo: need to fill this Maintainer email. +Maintainer: headerbidding@taboola.com ``` # Description From 0ada596156a6012c848e09a18012c96466ebd416 Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 7 Feb 2022 18:15:58 +0200 Subject: [PATCH 53/76] update the md --- modules/taboolaBidAdapter.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/modules/taboolaBidAdapter.md b/modules/taboolaBidAdapter.md index 554eb4de442..42871b20ed5 100644 --- a/modules/taboolaBidAdapter.md +++ b/modules/taboolaBidAdapter.md @@ -13,11 +13,6 @@ support display format. Using OpenRTB standard. # Configuration -## Bidder and usersync URLs - -The Outbrain adapter does not work without setting the correct bidder and usersync URLs. -You will receive the URLs when contacting us. - # Test Display Parameters ``` var adUnits = [{ From 8603f264f7a8d18d94f3ec393116f92488c007d5 Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 8 Feb 2022 09:12:02 +0200 Subject: [PATCH 54/76] update the Maintainer email --- modules/taboolaBidAdapter.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/taboolaBidAdapter.md b/modules/taboolaBidAdapter.md index 42871b20ed5..f809e66d9b8 100644 --- a/modules/taboolaBidAdapter.md +++ b/modules/taboolaBidAdapter.md @@ -3,7 +3,7 @@ ``` Module Name: Taboola Adapter Module Type: Bidder Adapter -Maintainer: headerbidding@taboola.com +Maintainer: prebid@taboola.com ``` # Description From e0a2675c8494375bc9e1e5456eb6ac5209c3c51b Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 8 Feb 2022 10:05:38 +0200 Subject: [PATCH 55/76] * update MD page * refactor code for better readability * small fix in UT --- modules/taboolaBidAdapter.js | 56 +++++++++++---------- modules/taboolaBidAdapter.md | 15 ++++-- test/spec/modules/taboolaBidAdapter_spec.js | 2 +- 3 files changed, 42 insertions(+), 31 deletions(-) diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js index 3d57d8c9b82..fda06eb09e3 100644 --- a/modules/taboolaBidAdapter.js +++ b/modules/taboolaBidAdapter.js @@ -60,33 +60,7 @@ export const spec = { return []; } - return bids.map((bid, id) => { - const bidResponse = bidResponses[id]; - if (!bidResponse) { - return; - } - - const {price: cpm, crid: creativeId, adm: ad, w: width, h: height, adomain: advertiserDomains, meta = {} - } = bidResponse; - - if (advertiserDomains && advertiserDomains.length > 0) { - meta.advertiserDomains = advertiserDomains - } - - return { - requestId: bid.bidId, - ttl: 360, - mediaType: BANNER, - cpm, - creativeId, - currency, - ad, - width, - height, - meta, - netRevenue: false - }; - }).filter(Boolean); + return bids.map((bid, id) => getBid(bid.bidId, currency, bidResponses[id])).filter(Boolean); }, }; @@ -151,3 +125,31 @@ function getBidResponses({body}) { cur }; } + +function getBid(requestId, currency, bidResponse) { + if (!bidResponse) { + return; + } + + const { + price: cpm, crid: creativeId, adm: ad, w: width, h: height, adomain: advertiserDomains, meta = {} + } = bidResponse; + + if (advertiserDomains && advertiserDomains.length > 0) { + meta.advertiserDomains = advertiserDomains + } + + return { + requestId, + ttl: 360, + mediaType: BANNER, + cpm, + creativeId, + currency, + ad, + width, + height, + meta, + netRevenue: false + }; +} diff --git a/modules/taboolaBidAdapter.md b/modules/taboolaBidAdapter.md index f809e66d9b8..31e77be2793 100644 --- a/modules/taboolaBidAdapter.md +++ b/modules/taboolaBidAdapter.md @@ -11,8 +11,6 @@ Maintainer: prebid@taboola.com Module that connects to Taboola bidder to fetch bids. support display format. Using OpenRTB standard. -# Configuration - # Test Display Parameters ``` var adUnits = [{ @@ -34,5 +32,16 @@ support display format. Using OpenRTB standard. } }] }]; - ``` + +# Parameters + +| Name | Scope | Description | Example | Type | +|---------------|----------|----------------------------------------------|--------------------------|--------------| +| `tagId` | required | Tag Id / Placement name | `below the article` | `String` | +| `publisherId` | required | Tag Id / Placement name | `below the article` | `String` | +| `bcat` | optional | list of blocked advertiser categories (IAB) | `['IAB1-1']` | `Array` | +| `badv` | optional | Blocked Advertiser Domains | `example.com` | `String Url` | +| `bidfloor` | optional | CPM bid floor | `0.25` | `Integer` | +| `bidfloorcur` | optional | CPM bid floor currency | `Euro` | `Integer` | + diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index 13572737211..2e035f11d0f 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -1,7 +1,7 @@ import {expect} from 'chai'; import {spec} from 'modules/taboolaBidAdapter.js'; -describe.only('Taboola Adapter', function () { +describe('Taboola Adapter', function () { const commonBidRequest = { bidder: 'taboola', params: { From 4ac1cd47bc7ee4e29492dfaf128ae0c4374e988f Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 8 Feb 2022 11:14:31 +0200 Subject: [PATCH 56/76] * add privacy to the request builder * add relevant Ut * small fixes in UT --- modules/taboolaBidAdapter.js | 34 +++++++++-- test/spec/modules/taboolaBidAdapter_spec.js | 67 ++++++++++++++++----- 2 files changed, 82 insertions(+), 19 deletions(-) diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js index fda06eb09e3..bf014c00479 100644 --- a/modules/taboolaBidAdapter.js +++ b/modules/taboolaBidAdapter.js @@ -3,6 +3,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER} from '../src/mediaTypes.js'; +import {config} from '../src/config.js'; const BIDDER_CODE = 'taboola'; const GVLID = 42; @@ -24,11 +25,31 @@ export const spec = { }, buildRequests: (validBidRequests, bidderRequest) => { const [bidRequest] = validBidRequests; + const {refererInfo, gdprConsent = {}, uspConsent} = bidderRequest; const {bcat = [], badv = [], publisherId} = bidRequest.params; - const site = getSiteProperties(bidRequest.params, bidderRequest.refererInfo.referer); + const site = getSiteProperties(bidRequest.params, refererInfo.referer); const device = {ua: navigator.userAgent}; - const timeout = bidderRequest.timeout; const imps = getImps(validBidRequests); + const user = { + ext: {} + }; + const regs = { + coppa: 0, + ext: {} + }; + + if (gdprConsent.gdprApplies) { + user.ext.consent = bidderRequest.gdprConsent.consentString; + regs.ext.gdpr = 1; + } + + if (uspConsent) { + regs.ext.us_privacy = uspConsent; + } + + if (config.getConfig('coppa')) { + regs.coppa = 1 + } const request = { id: bidderRequest.auctionId, @@ -36,10 +57,13 @@ export const spec = { site, device, source: {fd: 1}, - tmax: timeout, - bcat: bcat, - badv: badv + tmax: bidderRequest.timeout, + bcat, + badv, + user, + regs }; + const url = [END_POINT_URL, publisherId].join('?p='); return { diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index 2e035f11d0f..0d441dcba2b 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -1,5 +1,6 @@ import {expect} from 'chai'; import {spec} from 'modules/taboolaBidAdapter.js'; +import {config} from '../../../src/config' describe('Taboola Adapter', function () { const commonBidRequest = { @@ -66,6 +67,11 @@ describe('Taboola Adapter', function () { }) describe('buildRequests', function () { + const defaultBidRequest = { + ...commonBidRequest, + ...displayBidRequestParams, + } + const commonBidderRequest = { refererInfo: { referer: 'https://example.com/' @@ -73,10 +79,6 @@ describe('Taboola Adapter', function () { } it('should build display request', function () { - const bidRequest = { - ...commonBidRequest, - ...displayBidRequestParams, - } const expectedData = { 'imp': [{ 'id': 1, @@ -97,10 +99,12 @@ describe('Taboola Adapter', function () { 'device': {'ua': navigator.userAgent}, 'source': {'fd': 1}, 'bcat': [], - 'badv': [] + 'badv': [], + 'user': {'ext': {}}, + 'regs': {'coppa': 0, 'ext': {}} }; - const res = spec.buildRequests([bidRequest], commonBidderRequest) + const res = spec.buildRequests([defaultBidRequest], commonBidderRequest) expect(res.url).to.equal('http://taboolahb.bidder.taboolasyndication.com?p=publisherId') expect(res.data).to.deep.equal(JSON.stringify(expectedData)) @@ -114,8 +118,7 @@ describe('Taboola Adapter', function () { bidfloorcur: 'EUR' } const bidRequest = { - ...commonBidRequest, - ...displayBidRequestParams, + ...defaultBidRequest, params: {...commonBidRequest.params, ...optionalParams} } @@ -128,19 +131,55 @@ describe('Taboola Adapter', function () { }); it('should pass bidder timeout', function () { - const bidRequest = { - ...commonBidRequest, - ...displayBidRequestParams, - } - const bidderRequest = { ...commonBidderRequest, timeout: 500 } - const res = spec.buildRequests([bidRequest], bidderRequest) + const res = spec.buildRequests([defaultBidRequest], bidderRequest) const resData = JSON.parse(res.data) expect(resData.tmax).to.equal(500) }); + + describe('handle privacy segments when building request', function () { + it('should pass GDPR consent', function () { + const bidderRequest = { + refererInfo: { + referer: 'https://example.com/' + }, + gdprConsent: { + gdprApplies: true, + consentString: 'consentString', + } + }; + + const res = spec.buildRequests([defaultBidRequest], bidderRequest) + const resData = JSON.parse(res.data) + expect(resData.user.ext.consent).to.equal('consentString') + expect(resData.regs.ext.gdpr).to.equal(1) + }); + + it('should pass us privacy consent', function () { + const bidderRequest = { + refererInfo: { + referer: 'https://example.com/' + }, + uspConsent: 'consentString' + } + const res = spec.buildRequests([defaultBidRequest], bidderRequest) + const resData = JSON.parse(res.data) + expect(resData.regs.ext.us_privacy).to.equal('consentString') + }); + + it('should pass coppa consent', function () { + config.setConfig({coppa: true}) + + const res = spec.buildRequests([defaultBidRequest], commonBidderRequest) + const resData = JSON.parse(res.data); + expect(resData.regs.coppa).to.equal(1) + + config.resetConfig() + }); + }) }) describe('interpretResponse', function () { From 891fd1e1a69b4de0922281b4ee82c0a8c17464bb Mon Sep 17 00:00:00 2001 From: Michael Date: Wed, 9 Feb 2022 14:44:07 +0200 Subject: [PATCH 57/76] * code refactoring + add more accurate way to get page url and referer * add relevant Ut * small fixes in md --- modules/taboolaBidAdapter.js | 41 +++++++-- modules/taboolaBidAdapter.md | 16 ++-- test/spec/modules/taboolaBidAdapter_spec.js | 93 +++++++++++++++++++-- 3 files changed, 131 insertions(+), 19 deletions(-) diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js index bf014c00479..44863c81aef 100644 --- a/modules/taboolaBidAdapter.js +++ b/modules/taboolaBidAdapter.js @@ -4,6 +4,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; +import {getWindowSelf, getWindowTop} from '../src/utils.js' const BIDDER_CODE = 'taboola'; const GVLID = 42; @@ -27,10 +28,11 @@ export const spec = { const [bidRequest] = validBidRequests; const {refererInfo, gdprConsent = {}, uspConsent} = bidderRequest; const {bcat = [], badv = [], publisherId} = bidRequest.params; - const site = getSiteProperties(bidRequest.params, refererInfo.referer); + const site = getSiteProperties(bidRequest.params, refererInfo); const device = {ua: navigator.userAgent}; const imps = getImps(validBidRequests); const user = { + buyerid: window.TRC ? window.TRC.user_id : 0, ext: {} }; const regs = { @@ -64,7 +66,7 @@ export const spec = { regs }; - const url = [END_POINT_URL, publisherId].join('?p='); + const url = [END_POINT_URL, publisherId].join('?pid='); return { url, @@ -87,16 +89,45 @@ export const spec = { return bids.map((bid, id) => getBid(bid.bidId, currency, bidResponses[id])).filter(Boolean); }, }; +export const internal = { + getPageUrl: (refererInfo = {}) => { + if (refererInfo.canonicalUrl) { + return refererInfo.canonicalUrl; + } + + if (config.getConfig('pageUrl')) { + return config.getConfig('pageUrl'); + } + + try { + return getWindowTop().location.href; + } catch (e) { + return getWindowSelf().location.href; + } + }, + getReferrer: (refererInfo = {}) => { + if (refererInfo.referer) { + return refererInfo.referer; + } + + try { + return getWindowTop().document.referrer; + } catch (e) { + return getWindowSelf().document.referrer; + } + } +} registerBidder(spec); -function getSiteProperties({publisherId, bcat = []}, page) { +function getSiteProperties({publisherId, bcat = []}, refererInfo) { + const {getPageUrl, getReferrer} = internal; return { id: publisherId, name: publisherId, domain: window.location.host, - page, - cat: bcat, + page: getPageUrl(refererInfo), + ref: getReferrer(refererInfo), publisher: { id: publisherId }, diff --git a/modules/taboolaBidAdapter.md b/modules/taboolaBidAdapter.md index 31e77be2793..5a84ab378be 100644 --- a/modules/taboolaBidAdapter.md +++ b/modules/taboolaBidAdapter.md @@ -36,12 +36,12 @@ support display format. Using OpenRTB standard. # Parameters -| Name | Scope | Description | Example | Type | -|---------------|----------|----------------------------------------------|--------------------------|--------------| -| `tagId` | required | Tag Id / Placement name | `below the article` | `String` | -| `publisherId` | required | Tag Id / Placement name | `below the article` | `String` | -| `bcat` | optional | list of blocked advertiser categories (IAB) | `['IAB1-1']` | `Array` | -| `badv` | optional | Blocked Advertiser Domains | `example.com` | `String Url` | -| `bidfloor` | optional | CPM bid floor | `0.25` | `Integer` | -| `bidfloorcur` | optional | CPM bid floor currency | `Euro` | `Integer` | +| Name | Scope | Description | Example | Type | +|----------------|----------|-----------------------------------------------------|--------------------------|--------------| +| `tagId` | required | Tag Id / Placement name | `below the article` | `String` | +| `publisherId` | required | Publisher id | `Publisher name` | `String` | +| `bcat` | optional | list of blocked advertiser categories (IAB) | `['IAB1-1']` | `Array` | +| `badv` | optional | Blocked Advertiser Domains | `example.com` | `String Url` | +| `bidfloor` | optional | CPM bid floor | `0.25` | `Integer` | +| `bidfloorcur` | optional | CPM bid floor currency | `Euro` | `Integer` | diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index 0d441dcba2b..cb10875ff04 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -1,6 +1,7 @@ import {expect} from 'chai'; -import {spec} from 'modules/taboolaBidAdapter.js'; +import {spec, internal} from 'modules/taboolaBidAdapter.js'; import {config} from '../../../src/config' +import * as utils from '../../../src/utils' describe('Taboola Adapter', function () { const commonBidRequest = { @@ -74,7 +75,8 @@ describe('Taboola Adapter', function () { const commonBidderRequest = { refererInfo: { - referer: 'https://example.com/' + referer: 'https://example.com/ref', + canonicalUrl: 'https://example.com/' } } @@ -91,8 +93,8 @@ describe('Taboola Adapter', function () { 'id': 'publisherId', 'name': 'publisherId', 'domain': window.location.host, - 'page': 'https://example.com/', - 'cat': [], + 'page': commonBidderRequest.refererInfo.canonicalUrl, + 'ref': commonBidderRequest.refererInfo.referer, 'publisher': {'id': 'publisherId'}, 'content': {'language': 'en-US'} }, @@ -100,13 +102,16 @@ describe('Taboola Adapter', function () { 'source': {'fd': 1}, 'bcat': [], 'badv': [], - 'user': {'ext': {}}, + 'user': { + 'buyerid': 0, + 'ext': {}, + }, 'regs': {'coppa': 0, 'ext': {}} }; const res = spec.buildRequests([defaultBidRequest], commonBidderRequest) - expect(res.url).to.equal('http://taboolahb.bidder.taboolasyndication.com?p=publisherId') + expect(res.url).to.equal('http://taboolahb.bidder.taboolasyndication.com?pid=publisherId') expect(res.data).to.deep.equal(JSON.stringify(expectedData)) }) @@ -140,6 +145,20 @@ describe('Taboola Adapter', function () { expect(resData.tmax).to.equal(500) }); + it('should use Taboola user id if exist', function () { + window.TRC = { + user_id: 51525152 + } + const bidderRequest = { + ...commonBidderRequest, + timeout: 500 + } + const res = spec.buildRequests([defaultBidRequest], bidderRequest) + const resData = JSON.parse(res.data) + expect(resData.user.buyerid).to.equal(51525152) + delete window.TRC; + }); + describe('handle privacy segments when building request', function () { it('should pass GDPR consent', function () { const bidderRequest = { @@ -285,4 +304,66 @@ describe('Taboola Adapter', function () { describe('getUserSyncs', function () { // todo: add UT for getUserSyncs }) + + describe('internal functions', function () { + describe('getPageUrl', function() { + let origPageUrl; + const bidderRequest = { + refererInfo: { + canonicalUrl: 'http://canonical.url' + } + }; + + beforeEach(function() { + // remember original pageUrl in config + origPageUrl = config.getConfig('pageUrl'); + + // unset pageUrl in config + config.setConfig({ pageUrl: null }); + }); + + afterEach(function() { + // set original pageUrl to config + config.setConfig({ pageUrl: origPageUrl }); + }); + + it('should handle empty or missing data', function() { + expect(internal.getPageUrl(undefined)).to.equal(utils.getWindowTop().location.href); + expect(internal.getPageUrl('')).to.equal(utils.getWindowTop().location.href); + }); + + it('should use "pageUrl" from config', function() { + config.setConfig({ pageUrl: 'http://page.url' }); + + expect(internal.getPageUrl(undefined)).to.equal(config.getConfig('pageUrl')); + }); + + it('should use bidderRequest.refererInfo.canonicalUrl', function() { + expect(internal.getPageUrl(bidderRequest.refererInfo)).to.equal(bidderRequest.refererInfo.canonicalUrl); + }); + + it('should prefer bidderRequest.refererInfo.canonicalUrl over "pageUrl" from config', () => { + config.setConfig({ pageUrl: 'https://page.url' }); + + expect(internal.getPageUrl(bidderRequest.refererInfo)).to.equal(bidderRequest.refererInfo.canonicalUrl); + }); + }); + + describe('getReferrer', function() { + it('should handle empty or missing data', function() { + expect(internal.getReferrer(undefined)).to.equal(utils.getWindowTop().document.referrer); + expect(internal.getReferrer('')).to.equal(utils.getWindowTop().document.referrer); + }); + + it('should use bidderRequest.refererInfo.referer', () => { + const bidderRequest = { + refererInfo: { + referer: 'foobar' + } + }; + + expect(internal.getReferrer(bidderRequest.refererInfo)).to.equal(bidderRequest.refererInfo.referer); + }); + }); + }) }) From 1dd1f4fd6bd9351c4c7e13eda26054caa926f8d4 Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 10 Feb 2022 16:34:49 +0200 Subject: [PATCH 58/76] * code refactoring + gte user id * add relevant Ut * small fixes --- modules/taboolaBidAdapter.js | 117 +++++++++++----- test/spec/modules/taboolaBidAdapter_spec.js | 141 +++++++++++++++----- 2 files changed, 188 insertions(+), 70 deletions(-) diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js index 44863c81aef..34b751c5dbc 100644 --- a/modules/taboolaBidAdapter.js +++ b/modules/taboolaBidAdapter.js @@ -1,15 +1,91 @@ -// jshint esversion: 6, es3: false, node: true 'use strict'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; import {getWindowSelf, getWindowTop} from '../src/utils.js' +import {getStorageManager} from '../src/storageManager.js'; const BIDDER_CODE = 'taboola'; const GVLID = 42; const CURRENCY = 'USD'; -const END_POINT_URL = 'http://taboolahb.bidder.taboolasyndication.com' +export const END_POINT_URL = 'https://taboolahb.bidder.taboolasyndication.com'; +const USER_ID = 'user-id'; +const STORAGE_KEY = `taboola global:${USER_ID}`; +const COOKIE_KEY = 'trc_cookie_storage'; + +/** + * try to extract User Id by that order: + * local storage + * first party cookie + * rendered trc + * new user set it to 0 + */ +export const userData = { + storageManager: getStorageManager(GVLID, BIDDER_CODE), + getUserId: () => { + const {getFromLocalStorage, getFromCookie, getFromTRC} = userData; + + try { + return getFromLocalStorage() || getFromCookie() || getFromTRC(); + } catch (ex) { + return 0; + } + }, + getFromCookie() { + const {cookiesAreEnabled, getCookie} = userData.storageManager; + if (cookiesAreEnabled()) { + const cookieData = getCookie(COOKIE_KEY); + const userId = userData.getCookieDataByKey(cookieData, USER_ID); + if (userId) { + return userId; + } + } + }, + getCookieDataByKey(cookieData, key) { + const [, value = ''] = cookieData.split(`${key}=`) + return value; + }, + getFromLocalStorage() { + const {hasLocalStorage, localStorageIsEnabled, getDataFromLocalStorage} = userData.storageManager; + + if (hasLocalStorage() && localStorageIsEnabled()) { + return getDataFromLocalStorage(STORAGE_KEY); + } + }, + getFromTRC() { + return window.TRC ? window.TRC.user_id : 0; + } +} + +export const internal = { + getPageUrl: (refererInfo = {}) => { + if (refererInfo.canonicalUrl) { + return refererInfo.canonicalUrl; + } + + if (config.getConfig('pageUrl')) { + return config.getConfig('pageUrl'); + } + + try { + return getWindowTop().location.href; + } catch (e) { + return getWindowSelf().location.href; + } + }, + getReferrer: (refererInfo = {}) => { + if (refererInfo.referer) { + return refererInfo.referer; + } + + try { + return getWindowTop().document.referrer; + } catch (e) { + return getWindowSelf().document.referrer; + } + } +} export const spec = { supportedMediaTypes: [BANNER], @@ -21,9 +97,6 @@ export const spec = { bidRequest.params.publisherId && bidRequest.params.tagId); }, - getUserSyncs: (syncOptions, responses, gdprConsent, uspConsent) => { - return []; - }, buildRequests: (validBidRequests, bidderRequest) => { const [bidRequest] = validBidRequests; const {refererInfo, gdprConsent = {}, uspConsent} = bidderRequest; @@ -32,7 +105,7 @@ export const spec = { const device = {ua: navigator.userAgent}; const imps = getImps(validBidRequests); const user = { - buyerid: window.TRC ? window.TRC.user_id : 0, + buyeruid: userData.getUserId(gdprConsent, uspConsent), ext: {} }; const regs = { @@ -89,36 +162,6 @@ export const spec = { return bids.map((bid, id) => getBid(bid.bidId, currency, bidResponses[id])).filter(Boolean); }, }; -export const internal = { - getPageUrl: (refererInfo = {}) => { - if (refererInfo.canonicalUrl) { - return refererInfo.canonicalUrl; - } - - if (config.getConfig('pageUrl')) { - return config.getConfig('pageUrl'); - } - - try { - return getWindowTop().location.href; - } catch (e) { - return getWindowSelf().location.href; - } - }, - getReferrer: (refererInfo = {}) => { - if (refererInfo.referer) { - return refererInfo.referer; - } - - try { - return getWindowTop().document.referrer; - } catch (e) { - return getWindowSelf().document.referrer; - } - } -} - -registerBidder(spec); function getSiteProperties({publisherId, bcat = []}, refererInfo) { const {getPageUrl, getReferrer} = internal; @@ -208,3 +251,5 @@ function getBid(requestId, currency, bidResponse) { netRevenue: false }; } + +registerBidder(spec); diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index cb10875ff04..de1b4b30854 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -1,5 +1,5 @@ import {expect} from 'chai'; -import {spec, internal} from 'modules/taboolaBidAdapter.js'; +import {spec, internal, END_POINT_URL, userData} from 'modules/taboolaBidAdapter.js'; import {config} from '../../../src/config' import * as utils from '../../../src/utils' @@ -85,17 +85,17 @@ describe('Taboola Adapter', function () { 'imp': [{ 'id': 1, 'banner': [{'h': displayBidRequestParams.sizes[0][0], 'w': displayBidRequestParams.sizes[0][1]}], - 'tagid': 'placement name', + 'tagid': commonBidRequest.params.tagId, 'bidfloor': null, 'bidfloorcur': 'USD' }], 'site': { - 'id': 'publisherId', - 'name': 'publisherId', + 'id': commonBidRequest.params.publisherId, + 'name': commonBidRequest.params.publisherId, 'domain': window.location.host, 'page': commonBidderRequest.refererInfo.canonicalUrl, 'ref': commonBidderRequest.refererInfo.referer, - 'publisher': {'id': 'publisherId'}, + 'publisher': {'id': commonBidRequest.params.publisherId}, 'content': {'language': 'en-US'} }, 'device': {'ua': navigator.userAgent}, @@ -103,7 +103,7 @@ describe('Taboola Adapter', function () { 'bcat': [], 'badv': [], 'user': { - 'buyerid': 0, + 'buyeruid': 0, 'ext': {}, }, 'regs': {'coppa': 0, 'ext': {}} @@ -111,7 +111,7 @@ describe('Taboola Adapter', function () { const res = spec.buildRequests([defaultBidRequest], commonBidderRequest) - expect(res.url).to.equal('http://taboolahb.bidder.taboolasyndication.com?pid=publisherId') + expect(res.url).to.equal(`${END_POINT_URL}?pid=${commonBidRequest.params.publisherId}`) expect(res.data).to.deep.equal(JSON.stringify(expectedData)) }) @@ -145,20 +145,6 @@ describe('Taboola Adapter', function () { expect(resData.tmax).to.equal(500) }); - it('should use Taboola user id if exist', function () { - window.TRC = { - user_id: 51525152 - } - const bidderRequest = { - ...commonBidderRequest, - timeout: 500 - } - const res = spec.buildRequests([defaultBidRequest], bidderRequest) - const resData = JSON.parse(res.data) - expect(resData.user.buyerid).to.equal(51525152) - delete window.TRC; - }); - describe('handle privacy segments when building request', function () { it('should pass GDPR consent', function () { const bidderRequest = { @@ -199,6 +185,93 @@ describe('Taboola Adapter', function () { config.resetConfig() }); }) + + describe('handle userid ', function () { + it('should get user id from local storage', function () { + const getDataFromLocalStorage = sinon.stub(userData.storageManager, 'getDataFromLocalStorage'); + getDataFromLocalStorage.returns(51525152); + + const bidderRequest = { + ...commonBidderRequest, + timeout: 500 + } + const res = spec.buildRequests([defaultBidRequest], bidderRequest); + const resData = JSON.parse(res.data); + expect(resData.user.buyeruid).to.equal(51525152); + getDataFromLocalStorage.restore(); + }); + + it('should get user id from cookie if local storage isn`t defined', function () { + const hasLocalStorage = sinon.stub(userData.storageManager, 'hasLocalStorage'); + const getDataFromLocalStorage = sinon.stub(userData.storageManager, 'getDataFromLocalStorage'); + const getCookie = sinon.stub(userData.storageManager, 'getCookie'); + + getDataFromLocalStorage.returns(51525152); + hasLocalStorage.returns(false); + getCookie.returns('taboola%20global%3Auser-id=12121212'); + + const bidderRequest = { + ...commonBidderRequest + }; + const res = spec.buildRequests([defaultBidRequest], bidderRequest); + const resData = JSON.parse(res.data); + expect(resData.user.buyeruid).to.equal('12121212'); + + getDataFromLocalStorage.restore(); + hasLocalStorage.restore(); + getCookie.restore(); + }); + + it('should get user id from TRC if local storage and cookie isn`t defined', function () { + const hasLocalStorage = sinon.stub(userData.storageManager, 'hasLocalStorage'); + const cookiesAreEnabled = sinon.stub(userData.storageManager, 'cookiesAreEnabled'); + + hasLocalStorage.returns(false); + cookiesAreEnabled.returns(false); + + window.TRC = { + user_id: 31313131 + }; + + const bidderRequest = { + ...commonBidderRequest + } + const res = spec.buildRequests([defaultBidRequest], bidderRequest); + const resData = JSON.parse(res.data); + expect(resData.user.buyeruid).to.equal(31313131); + + hasLocalStorage.restore(); + cookiesAreEnabled.restore(); + delete window.TRC; + }); + + it('should get user id to be 0 if cookie, local storage, TRC isn`t defined', function () { + const hasLocalStorage = sinon.stub(userData.storageManager, 'hasLocalStorage'); + const cookiesAreEnabled = sinon.stub(userData.storageManager, 'cookiesAreEnabled'); + + hasLocalStorage.returns(false); + cookiesAreEnabled.returns(false); + + const bidderRequest = { + ...commonBidderRequest + } + const res = spec.buildRequests([defaultBidRequest], bidderRequest); + const resData = JSON.parse(res.data); + expect(resData.user.buyeruid).to.equal(0); + + hasLocalStorage.restore(); + cookiesAreEnabled.restore(); + }); + + it('should set buyeruid to be 0 if it`s a new user', function () { + const bidderRequest = { + ...commonBidderRequest + } + const res = spec.buildRequests([defaultBidRequest], bidderRequest); + const resData = JSON.parse(res.data); + expect(resData.user.buyeruid).to.equal(0); + }); + }); }) describe('interpretResponse', function () { @@ -301,12 +374,12 @@ describe('Taboola Adapter', function () { }); }) - describe('getUserSyncs', function () { + describe('userData', function () { // todo: add UT for getUserSyncs }) describe('internal functions', function () { - describe('getPageUrl', function() { + describe('getPageUrl', function () { let origPageUrl; const bidderRequest = { refererInfo: { @@ -314,43 +387,43 @@ describe('Taboola Adapter', function () { } }; - beforeEach(function() { + beforeEach(function () { // remember original pageUrl in config origPageUrl = config.getConfig('pageUrl'); // unset pageUrl in config - config.setConfig({ pageUrl: null }); + config.setConfig({pageUrl: null}); }); - afterEach(function() { + afterEach(function () { // set original pageUrl to config - config.setConfig({ pageUrl: origPageUrl }); + config.setConfig({pageUrl: origPageUrl}); }); - it('should handle empty or missing data', function() { + it('should handle empty or missing data', function () { expect(internal.getPageUrl(undefined)).to.equal(utils.getWindowTop().location.href); expect(internal.getPageUrl('')).to.equal(utils.getWindowTop().location.href); }); - it('should use "pageUrl" from config', function() { - config.setConfig({ pageUrl: 'http://page.url' }); + it('should use "pageUrl" from config', function () { + config.setConfig({pageUrl: 'http://page.url'}); expect(internal.getPageUrl(undefined)).to.equal(config.getConfig('pageUrl')); }); - it('should use bidderRequest.refererInfo.canonicalUrl', function() { + it('should use bidderRequest.refererInfo.canonicalUrl', function () { expect(internal.getPageUrl(bidderRequest.refererInfo)).to.equal(bidderRequest.refererInfo.canonicalUrl); }); it('should prefer bidderRequest.refererInfo.canonicalUrl over "pageUrl" from config', () => { - config.setConfig({ pageUrl: 'https://page.url' }); + config.setConfig({pageUrl: 'https://page.url'}); expect(internal.getPageUrl(bidderRequest.refererInfo)).to.equal(bidderRequest.refererInfo.canonicalUrl); }); }); - describe('getReferrer', function() { - it('should handle empty or missing data', function() { + describe('getReferrer', function () { + it('should handle empty or missing data', function () { expect(internal.getReferrer(undefined)).to.equal(utils.getWindowTop().document.referrer); expect(internal.getReferrer('')).to.equal(utils.getWindowTop().document.referrer); }); From b99902b7a90e4baf48011f63f07778721ef02f40 Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 13 Feb 2022 16:16:27 +0200 Subject: [PATCH 59/76] * code refactoring + gte user id * add relevant Ut * small fixes --- modules/taboolaBidAdapter.js | 15 +++++---------- test/spec/modules/taboolaBidAdapter_spec.js | 5 ++++- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js index 34b751c5dbc..d1c5628184d 100644 --- a/modules/taboolaBidAdapter.js +++ b/modules/taboolaBidAdapter.js @@ -195,16 +195,11 @@ function getImps(validBidRequests) { } function getBanners(bid) { - return getSizes(bid.sizes); -} - -function getSizes(sizes) { - return sizes.map(size => { - return { - h: size[0], - w: size[1] - } - }) + const [size] = bid.sizes; + return { + h: size[0], + w: size[1] + } } function getBidResponses({body}) { diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index de1b4b30854..57d49cb9f1c 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -84,7 +84,10 @@ describe('Taboola Adapter', function () { const expectedData = { 'imp': [{ 'id': 1, - 'banner': [{'h': displayBidRequestParams.sizes[0][0], 'w': displayBidRequestParams.sizes[0][1]}], + 'banner': { + 'h': displayBidRequestParams.sizes[0][0], + 'w': displayBidRequestParams.sizes[0][1] + }, 'tagid': commonBidRequest.params.tagId, 'bidfloor': null, 'bidfloorcur': 'USD' From dd2c61fe5f3bee09c8d9e862cf0ea40844539c40 Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 15 Feb 2022 16:09:56 +0200 Subject: [PATCH 60/76] * update end point url * update UT * Update banner End point structure --- modules/taboolaBidAdapter.js | 23 +++++--- modules/taboolaBidAdapter.md | 60 +++++++++++---------- test/spec/modules/taboolaBidAdapter_spec.js | 15 +++--- 3 files changed, 55 insertions(+), 43 deletions(-) diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js index d1c5628184d..d0d9db415f9 100644 --- a/modules/taboolaBidAdapter.js +++ b/modules/taboolaBidAdapter.js @@ -9,7 +9,7 @@ import {getStorageManager} from '../src/storageManager.js'; const BIDDER_CODE = 'taboola'; const GVLID = 42; const CURRENCY = 'USD'; -export const END_POINT_URL = 'https://taboolahb.bidder.taboolasyndication.com'; +export const END_POINT_URL = 'hb.bidder.taboola.com/TaboolaHBOpenRTBRequestHandlerServlet'; const USER_ID = 'user-id'; const STORAGE_KEY = `taboola global:${USER_ID}`; const COOKIE_KEY = 'trc_cookie_storage'; @@ -93,9 +93,9 @@ export const spec = { code: BIDDER_CODE, isBidRequestValid: (bidRequest) => { return !!(bidRequest.sizes && - bidRequest.params && - bidRequest.params.publisherId && - bidRequest.params.tagId); + bidRequest.params && + bidRequest.params.publisherId && + bidRequest.params.tagId); }, buildRequests: (validBidRequests, bidderRequest) => { const [bidRequest] = validBidRequests; @@ -139,7 +139,7 @@ export const spec = { regs }; - const url = [END_POINT_URL, publisherId].join('?pid='); + const url = [END_POINT_URL, publisherId].join(''); return { url, @@ -195,10 +195,17 @@ function getImps(validBidRequests) { } function getBanners(bid) { - const [size] = bid.sizes; + return getSizes(bid.sizes); +} + +function getSizes(sizes) { return { - h: size[0], - w: size[1] + format: sizes.map(size => { + return { + h: size[0], + w: size[1] + } + }) } } diff --git a/modules/taboolaBidAdapter.md b/modules/taboolaBidAdapter.md index 5a84ab378be..d02cdf4126a 100644 --- a/modules/taboolaBidAdapter.md +++ b/modules/taboolaBidAdapter.md @@ -9,39 +9,41 @@ Maintainer: prebid@taboola.com # Description Module that connects to Taboola bidder to fetch bids. -support display format. Using OpenRTB standard. +- Supports ‘display’ format +- Uses OpenRTB standard + +The Taboola Bidding adapter requires setup before beginning. Please contact us on prebid@taboola.com # Test Display Parameters -``` - var adUnits = [{ - code: 'your-unit-container-id', - mediaTypes: { - banner: { - sizes: [[300, 250], [300,600]], - } - }, - bids: [{ - bidder: 'taboola', - params: { - tagId: 'test-1', - publisherId: 'test', - bidfloor: 0.25, // optional default is null - bidfloorcur: 'USD', // optional default is USD - bcat: ['IAB1-1'], // optional default is [] - badv: ['example.com'] // optional default is [] - } - }] - }]; +``` javascript + var adUnits = [{ + code: 'your-unit-container-id', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]] + } + }, + bids: [{ + bidder: 'taboola', + params: { + tagId: 'Placement Name', + publisherId: 'your-publisher-id', + bidfloor: 0.25, // Optional - default is null + bcat: ['IAB1-1'], // Optional - default is [] + badv: ['example.com'] // Optional - default is [] + } + }] +}]; ``` # Parameters -| Name | Scope | Description | Example | Type | -|----------------|----------|-----------------------------------------------------|--------------------------|--------------| -| `tagId` | required | Tag Id / Placement name | `below the article` | `String` | -| `publisherId` | required | Publisher id | `Publisher name` | `String` | -| `bcat` | optional | list of blocked advertiser categories (IAB) | `['IAB1-1']` | `Array` | -| `badv` | optional | Blocked Advertiser Domains | `example.com` | `String Url` | -| `bidfloor` | optional | CPM bid floor | `0.25` | `Integer` | -| `bidfloorcur` | optional | CPM bid floor currency | `Euro` | `Integer` | +| Name | Scope | Description | Example | Type | +|----------------|----------|---------------------------------------------------------|----------------------------|--------------| +| `tagId` | required | Tag ID / Placement Name
(as provided by Taboola) | `'Below The Article'` | `String` | +| `publisherId` | required | Alphabetic Publisher ID
(as provided by Taboola) | `'acme-publishing'` | `String` | +| `bcat` | optional | List of blocked advertiser categories (IAB) | `['IAB1-1']` | `Array` | +| `badv` | optional | Blocked Advertiser Domains | `'example.com'` | `String Url` | +| `bidfloor` | optional | CPM bid floor | `0.25` | `Integer` | + diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index 57d49cb9f1c..e5a34dfb36d 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -15,9 +15,7 @@ describe('Taboola Adapter', function () { } const displayBidRequestParams = { - sizes: [ - [300, 250] - ] + sizes: [[300, 250], [300, 600]] } describe('isBidRequestValid', function () { @@ -85,8 +83,13 @@ describe('Taboola Adapter', function () { 'imp': [{ 'id': 1, 'banner': { - 'h': displayBidRequestParams.sizes[0][0], - 'w': displayBidRequestParams.sizes[0][1] + format: [{ + h: displayBidRequestParams.sizes[0][0], + w: displayBidRequestParams.sizes[0][1]}, + { + h: displayBidRequestParams.sizes[1][0], + w: displayBidRequestParams.sizes[1][1]} + ] }, 'tagid': commonBidRequest.params.tagId, 'bidfloor': null, @@ -114,7 +117,7 @@ describe('Taboola Adapter', function () { const res = spec.buildRequests([defaultBidRequest], commonBidderRequest) - expect(res.url).to.equal(`${END_POINT_URL}?pid=${commonBidRequest.params.publisherId}`) + expect(res.url).to.equal(`${END_POINT_URL}${commonBidRequest.params.publisherId}`) expect(res.data).to.deep.equal(JSON.stringify(expectedData)) }) From 4d00a145303b91fbedfd38c0338086c491b6dd66 Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 21 Feb 2022 10:09:29 +0200 Subject: [PATCH 61/76] small fixes + update epi url --- modules/taboolaBidAdapter.js | 14 +++++++------- modules/taboolaBidAdapter.md | 4 ++-- test/spec/modules/taboolaBidAdapter_spec.js | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js index d0d9db415f9..534edcc6e63 100644 --- a/modules/taboolaBidAdapter.js +++ b/modules/taboolaBidAdapter.js @@ -9,17 +9,17 @@ import {getStorageManager} from '../src/storageManager.js'; const BIDDER_CODE = 'taboola'; const GVLID = 42; const CURRENCY = 'USD'; -export const END_POINT_URL = 'hb.bidder.taboola.com/TaboolaHBOpenRTBRequestHandlerServlet'; +export const END_POINT_URL = 'http://hb.bidder.taboola.com/TaboolaHBOpenRTBRequestHandlerServlet'; const USER_ID = 'user-id'; const STORAGE_KEY = `taboola global:${USER_ID}`; const COOKIE_KEY = 'trc_cookie_storage'; /** - * try to extract User Id by that order: - * local storage - * first party cookie - * rendered trc - * new user set it to 0 + * extract User Id by that order: + * 1. local storage + * 2. first party cookie + * 3. rendered trc + * 4. new user set it to 0 */ export const userData = { storageManager: getStorageManager(GVLID, BIDDER_CODE), @@ -139,7 +139,7 @@ export const spec = { regs }; - const url = [END_POINT_URL, publisherId].join(''); + const url = [END_POINT_URL, publisherId].join('/'); return { url, diff --git a/modules/taboolaBidAdapter.md b/modules/taboolaBidAdapter.md index d02cdf4126a..a3cd1d9261b 100644 --- a/modules/taboolaBidAdapter.md +++ b/modules/taboolaBidAdapter.md @@ -9,8 +9,8 @@ Maintainer: prebid@taboola.com # Description Module that connects to Taboola bidder to fetch bids. -- Supports ‘display’ format -- Uses OpenRTB standard +- Supports `display` format +- Uses `OpenRTB` standard The Taboola Bidding adapter requires setup before beginning. Please contact us on prebid@taboola.com diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index e5a34dfb36d..f6cbee45668 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -117,7 +117,7 @@ describe('Taboola Adapter', function () { const res = spec.buildRequests([defaultBidRequest], commonBidderRequest) - expect(res.url).to.equal(`${END_POINT_URL}${commonBidRequest.params.publisherId}`) + expect(res.url).to.equal(`${END_POINT_URL}/${commonBidRequest.params.publisherId}`) expect(res.data).to.deep.equal(JSON.stringify(expectedData)) }) From 8edfa53a919fcdf29db67366bb49af2ebd9a2387 Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 8 May 2022 14:40:06 +0300 Subject: [PATCH 62/76] remove the destruction from the bidResponse property --- modules/taboolaBidAdapter.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js index 534edcc6e63..a35f75c8079 100644 --- a/modules/taboolaBidAdapter.js +++ b/modules/taboolaBidAdapter.js @@ -210,11 +210,11 @@ function getSizes(sizes) { } function getBidResponses({body}) { - if (!body || (body && !body.bidResponse)) { + if (!body) { return []; } - const {seatbid, cur} = body.bidResponse; + const {seatbid, cur} = body; if (!seatbid.length || !seatbid[0].bid) { return []; From 306c2f77f7e4e7142166eb5c365906d193cae6b5 Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 8 May 2022 15:14:37 +0300 Subject: [PATCH 63/76] (update the unit tests) remove the destruction from the bidResponse property --- modules/taboolaBidAdapter.js | 2 +- test/spec/modules/taboolaBidAdapter_spec.js | 75 ++++++++++----------- 2 files changed, 38 insertions(+), 39 deletions(-) diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js index a35f75c8079..cb6a6048d7f 100644 --- a/modules/taboolaBidAdapter.js +++ b/modules/taboolaBidAdapter.js @@ -22,7 +22,7 @@ const COOKIE_KEY = 'trc_cookie_storage'; * 4. new user set it to 0 */ export const userData = { - storageManager: getStorageManager(GVLID, BIDDER_CODE), + storageManager: getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}), getUserId: () => { const {getFromLocalStorage, getFromCookie, getFromTRC} = userData; diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index f6cbee45668..86e32843228 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -85,10 +85,12 @@ describe('Taboola Adapter', function () { 'banner': { format: [{ h: displayBidRequestParams.sizes[0][0], - w: displayBidRequestParams.sizes[0][1]}, + w: displayBidRequestParams.sizes[0][1] + }, { h: displayBidRequestParams.sizes[1][0], - w: displayBidRequestParams.sizes[1][1]} + w: displayBidRequestParams.sizes[1][1] + } ] }, 'tagid': commonBidRequest.params.tagId, @@ -283,34 +285,31 @@ describe('Taboola Adapter', function () { describe('interpretResponse', function () { const serverResponse = { body: { - 'bidResponse': { - 'id': '49ffg4d58ef9a163a69fhgfghd4fad03621b9e036f24f7_15', - 'seatbid': [ - { - 'bid': [ - { - 'id': '0b3dd94348-134b-435f-8db5-6bf5afgfc39e86c', - 'impid': '1', - 'price': 0.342068, - 'adid': '2785119545551083381', - 'adm': '\u003chtml\u003e\n\u003chead\u003e\n\u003cmeta charset\u003d"UTF-8"\u003e\n\u003cmeta http-equiv\u003d"Content-Type" content\u003d"text/html; charset\u003dutf-8"/\u003e\u003c/head\u003e\n\u003cbody style\u003d"margin: 0px; overflow:hidden;"\u003e \n\u003cscript type\u003d"text/javascript"\u003e\nwindow.tbl_trc_domain \u003d \u0027us-trc.taboola.com\u0027;\nwindow._taboola \u003d window._taboola || [];\n_taboola.push({article:\u0027auto\u0027});\n!function (e, f, u, i) {\nif (!document.getElementById(i)){\ne.async \u003d 1;\ne.src \u003d u;\ne.id \u003d i;\nf.parentNode.insertBefore(e, f);\n}\n}(document.createElement(\u0027script\u0027),\ndocument.getElementsByTagName(\u0027script\u0027)[0],\n\u0027//cdn.taboola.com/libtrc/wattpad-placement-255/loader.js\u0027,\n\u0027tb_loader_script\u0027);\nif(window.performance \u0026\u0026 typeof window.performance.mark \u003d\u003d \u0027function\u0027)\n{window.performance.mark(\u0027tbl_ic\u0027);}\n\u003c/script\u003e\n\n\u003cdiv id\u003d"taboola-below-article-thumbnails" style\u003d"height: 250px; width: 300px;"\u003e\u003c/div\u003e\n\u003cscript type\u003d"text/javascript"\u003e\nwindow._taboola \u003d window._taboola || [];\n_taboola.push({\nmode: \u0027Rbox_300x250_1x1\u0027,\ncontainer: \u0027taboola-below-article-thumbnails\u0027,\nplacement: \u0027wattpad.com_P18694_S257846_W300_H250_N1_TB\u0027,\ntarget_type: \u0027mix\u0027,\n"rtb-win":{ \nbi:\u002749ff4d58ef9a163a696d4fad03621b9e036f24f7_15\u0027,\ncu:\u0027USD\u0027,\nwp:\u0027${AUCTION_PRICE:BF}\u0027,\nwcb:\u0027~!audex-display-impression!~\u0027,\nrt:\u00271643227025284\u0027,\nrdc:\u0027us.taboolasyndication.com\u0027,\nti:\u00274212\u0027,\nex:\u0027MagniteSCoD\u0027,\nbs:\u0027xapi:257846:lvvSm6Ak7_wE\u0027,\nbp:\u002718694\u0027,\nbd:\u0027wattpad.com\u0027,\nsi:\u00279964\u0027\n} \n,\nrec: {"trc":{"si":"a69c7df43b2334f0aa337c37e2d80c21","sd":"v2_a69c7df43b2334f0aa337c37e2d80c21_3c70f7c7d64a65b15e4a4175c9a2cfa51072f04bMagniteSCoD_1643227025_1643227025_CJS1tQEQ5NdWGPLA0d76xo-9ngEgASgEMCY4iegHQIroB0iB09kDUKPPB1gAYABop-G2i_Hl-eVucAA","ui":"3c70f7c7d64a65b15e4a4175c9a2cfa51072f04bMagniteSCoD","plc":"PHON","wi":"-643136642229425433","cc":"CA","route":"US:US:V","el2r":["bulk-metrics","debug","social","metrics","perf"],"uvpw":"1","pi":"1420260","cpb":"GNO629MGIJz__________wEqGXVzLnRhYm9vbGFzeW5kaWNhdGlvbi5jb20yC3RyYy1zY29kMTI5OIDwmrUMQInoB0iK6AdQgdPZA1ijzwdjCN3__________wEQ3f__________ARgjZGMI3AoQoBAYFmRjCNIDEOAGGAhkYwiWFBCcHBgYZGMI9AUQiwoYC2RjCNkUEPkcGB1kYwj0FBCeHRgfZGorNDlmZjRkNThlZjlhMTYzYTY5NmQ0ZmFkMDM2MjFiOWUwMzZmMjRmN18xNXgCgAHpbIgBrPvTxQE","dcga":{"pubConfigOverride":{"border-color":"black","font-weight":"bold","inherit-title-color":"true","module-name":"cta-lazy-module","enable-call-to-action-creative-component":"true","disable-cta-on-custom-module":"true"}},"tslt":{"p-video-overlay":{"cancel":"סגור","goto":"עבור לדף"},"read-more":{"DEFAULT_CAPTION":"%D7%A7%D7%A8%D7%90%20%D7%A2%D7%95%D7%93"},"next-up":{"BTN_TEXT":"לקריאת התוכן הבא"},"time-ago":{"now":"עכשיו","today":"היום","yesterday":"אתמול","minutes":"לפני {0} דקות","hour":"לפני שעה","hours":"לפני {0} שעות","days":"לפני {0} ימים"},"explore-more":{"TITLE_TEXT":"המשיכו לקרוא","POPUP_TEXT":"אל תפספסו הזדמנות לקרוא עוד תוכן מעולה, רגע לפני שתעזבו"}},"evh":"-1964913910","vl":[{"ri":"185db6d274ce94b27caaabd9eed7915b","uip":"wattpad.com_P18694_S257846_W300_H250_N1_TB","ppb":"COIF","estimation_method":"EcpmEstimationMethodType_ESTIMATION","baseline_variant":"false","original_ecpm":"0.4750949889421463","v":[{"thumbnail":"https://cdn.taboola.com/libtrc/static/thumbnails/a2b272be514ca3ebe3f97a4a32a41db5.jpg","all-thumbnails":"https://cdn.taboola.com/libtrc/static/thumbnails/a2b272be514ca3ebe3f97a4a32a41db5.jpg!-#@1600x1000","origin":"default","thumb-size":"1600x1000","title":"Get Roofing Services At Prices You Can Afford In Edmonton","type":"text","published-date":"1641997069","branding-text":"Roofing Services | Search Ads","url":"https://inneth-conded.xyz/9ad2e613-8777-4fe7-9a52-386c88879289?site\u003dwattpad-placement-255\u0026site_id\u003d1420260\u0026title\u003dGet+Roofing+Services+At+Prices+You+Can+Afford+In+Edmonton\u0026platform\u003dSmartphone\u0026campaign_id\u003d15573949\u0026campaign_item_id\u003d3108610633\u0026thumbnail\u003dhttp%3A%2F%2Fcdn.taboola.com%2Flibtrc%2Fstatic%2Fthumbnails%2Fa2b272be514ca3ebe3f97a4a32a41db5.jpg\u0026cpc\u003d{cpc}\u0026click_id\u003dGiCIypnAQogsMTFL3e_mPaVM2qLvK3KRU6LWzEMUgeB6piCit1Uox6CNr5v5n-x1\u0026tblci\u003dGiCIypnAQogsMTFL3e_mPaVM2qLvK3KRU6LWzEMUgeB6piCit1Uox6CNr5v5n-x1#tblciGiCIypnAQogsMTFL3e_mPaVM2qLvK3KRU6LWzEMUgeB6piCit1Uox6CNr5v5n-x1","duration":"0","sig":"328243c4127ff16e3fdcd7270bab908f6f3fc5b4c98d","item-id":"~~V1~~2785119550041083381~~PnBkfBE9JnQxpahv0adkcuIcmMhroRAHXwLZd-7zhunTxvAnL2wqac4MyzR7uD46gj3kUkbS3FhelBtnsiJV6MhkDZRZzzIqDobN6rWmCPA3hYz5D3PLat6nhIftiT1lwdxwdlxkeV_Mfb3eos_TQavImGhxk0e7psNAZxHJ9RKL2w3lppALGgQJoy2o6lkf-pOqODtX1VkgWpEEM4WsVoWOnUTAwdyGd-8yrze8CWNp752y28hl7lleicyO1vByRdbgwlJdnqyroTPEQNNEn1JRxBOSYSWt-Xm3vkPm-G4","uploader":"","is-syndicated":"true","publisher":"search","id":"~~V1~~2785119550041083381~~PnBkfBE9JnQxpahv0adkcuIcmMhroRAHXwLZd-7zhunTxvAnL2wqac4MyzR7uD46gj3kUkbS3FhelBtnsiJV6MhkDZRZzzIqDobN6rWmCPA3hYz5D3PLat6nhIftiT1lwdxwdlxkeV_Mfb3eos_TQavImGhxk0e7psNAZxHJ9RKL2w3lppALGgQJoy2o6lkf-pOqODtX1VkgWpEEM4WsVoWOnUTAwdyGd-8yrze8CWNp752y28hl7lleicyO1vByRdbgwlJdnqyroTPEQNNEn1JRxBOSYSWt-Xm3vkPm-G4","category":"home","views":"0","itp":[{"u":"https://trc.taboola.com/1326786/log/3/unip?en\u003dclickersusa","t":"c"}],"description":""}]}],"cpcud":{"upc":"0.0","upr":"0.0"}}}\n});\n\u003c/script\u003e\n\n\u003cscript type\u003d"text/javascript"\u003e\nwindow._taboola \u003d window._taboola || [];\n_taboola.push({flush: true});\n\u003c/script\u003e\n\n\u003c/body\u003e\n\u003c/html\u003e', - 'adomain': [ - 'example.xyz' - ], - 'cid': '15744349', - 'crid': '278195503434041083381', - 'w': 300, - 'h': 250, - 'lurl': 'http://us-trc.taboola.com/sample' - } - ], - 'seat': '14204545260' - } - ], - 'bidid': 'da43860a-4644-442a-b5e0-93f268cf8d19', - 'cur': 'USD' - }, - 'debugResponse': {} + 'id': '49ffg4d58ef9a163a69fhgfghd4fad03621b9e036f24f7_15', + 'seatbid': [ + { + 'bid': [ + { + 'id': '0b3dd94348-134b-435f-8db5-6bf5afgfc39e86c', + 'impid': '1', + 'price': 0.342068, + 'adid': '2785119545551083381', + 'adm': '\u003chtml\u003e\n\u003chead\u003e\n\u003cmeta charset\u003d"UTF-8"\u003e\n\u003cmeta http-equiv\u003d"Content-Type" content\u003d"text/html; charset\u003dutf-8"/\u003e\u003c/head\u003e\n\u003cbody style\u003d"margin: 0px; overflow:hidden;"\u003e \n\u003cscript type\u003d"text/javascript"\u003e\nwindow.tbl_trc_domain \u003d \u0027us-trc.taboola.com\u0027;\nwindow._taboola \u003d window._taboola || [];\n_taboola.push({article:\u0027auto\u0027});\n!function (e, f, u, i) {\nif (!document.getElementById(i)){\ne.async \u003d 1;\ne.src \u003d u;\ne.id \u003d i;\nf.parentNode.insertBefore(e, f);\n}\n}(document.createElement(\u0027script\u0027),\ndocument.getElementsByTagName(\u0027script\u0027)[0],\n\u0027//cdn.taboola.com/libtrc/wattpad-placement-255/loader.js\u0027,\n\u0027tb_loader_script\u0027);\nif(window.performance \u0026\u0026 typeof window.performance.mark \u003d\u003d \u0027function\u0027)\n{window.performance.mark(\u0027tbl_ic\u0027);}\n\u003c/script\u003e\n\n\u003cdiv id\u003d"taboola-below-article-thumbnails" style\u003d"height: 250px; width: 300px;"\u003e\u003c/div\u003e\n\u003cscript type\u003d"text/javascript"\u003e\nwindow._taboola \u003d window._taboola || [];\n_taboola.push({\nmode: \u0027Rbox_300x250_1x1\u0027,\ncontainer: \u0027taboola-below-article-thumbnails\u0027,\nplacement: \u0027wattpad.com_P18694_S257846_W300_H250_N1_TB\u0027,\ntarget_type: \u0027mix\u0027,\n"rtb-win":{ \nbi:\u002749ff4d58ef9a163a696d4fad03621b9e036f24f7_15\u0027,\ncu:\u0027USD\u0027,\nwp:\u0027${AUCTION_PRICE:BF}\u0027,\nwcb:\u0027~!audex-display-impression!~\u0027,\nrt:\u00271643227025284\u0027,\nrdc:\u0027us.taboolasyndication.com\u0027,\nti:\u00274212\u0027,\nex:\u0027MagniteSCoD\u0027,\nbs:\u0027xapi:257846:lvvSm6Ak7_wE\u0027,\nbp:\u002718694\u0027,\nbd:\u0027wattpad.com\u0027,\nsi:\u00279964\u0027\n} \n,\nrec: {"trc":{"si":"a69c7df43b2334f0aa337c37e2d80c21","sd":"v2_a69c7df43b2334f0aa337c37e2d80c21_3c70f7c7d64a65b15e4a4175c9a2cfa51072f04bMagniteSCoD_1643227025_1643227025_CJS1tQEQ5NdWGPLA0d76xo-9ngEgASgEMCY4iegHQIroB0iB09kDUKPPB1gAYABop-G2i_Hl-eVucAA","ui":"3c70f7c7d64a65b15e4a4175c9a2cfa51072f04bMagniteSCoD","plc":"PHON","wi":"-643136642229425433","cc":"CA","route":"US:US:V","el2r":["bulk-metrics","debug","social","metrics","perf"],"uvpw":"1","pi":"1420260","cpb":"GNO629MGIJz__________wEqGXVzLnRhYm9vbGFzeW5kaWNhdGlvbi5jb20yC3RyYy1zY29kMTI5OIDwmrUMQInoB0iK6AdQgdPZA1ijzwdjCN3__________wEQ3f__________ARgjZGMI3AoQoBAYFmRjCNIDEOAGGAhkYwiWFBCcHBgYZGMI9AUQiwoYC2RjCNkUEPkcGB1kYwj0FBCeHRgfZGorNDlmZjRkNThlZjlhMTYzYTY5NmQ0ZmFkMDM2MjFiOWUwMzZmMjRmN18xNXgCgAHpbIgBrPvTxQE","dcga":{"pubConfigOverride":{"border-color":"black","font-weight":"bold","inherit-title-color":"true","module-name":"cta-lazy-module","enable-call-to-action-creative-component":"true","disable-cta-on-custom-module":"true"}},"tslt":{"p-video-overlay":{"cancel":"סגור","goto":"עבור לדף"},"read-more":{"DEFAULT_CAPTION":"%D7%A7%D7%A8%D7%90%20%D7%A2%D7%95%D7%93"},"next-up":{"BTN_TEXT":"לקריאת התוכן הבא"},"time-ago":{"now":"עכשיו","today":"היום","yesterday":"אתמול","minutes":"לפני {0} דקות","hour":"לפני שעה","hours":"לפני {0} שעות","days":"לפני {0} ימים"},"explore-more":{"TITLE_TEXT":"המשיכו לקרוא","POPUP_TEXT":"אל תפספסו הזדמנות לקרוא עוד תוכן מעולה, רגע לפני שתעזבו"}},"evh":"-1964913910","vl":[{"ri":"185db6d274ce94b27caaabd9eed7915b","uip":"wattpad.com_P18694_S257846_W300_H250_N1_TB","ppb":"COIF","estimation_method":"EcpmEstimationMethodType_ESTIMATION","baseline_variant":"false","original_ecpm":"0.4750949889421463","v":[{"thumbnail":"https://cdn.taboola.com/libtrc/static/thumbnails/a2b272be514ca3ebe3f97a4a32a41db5.jpg","all-thumbnails":"https://cdn.taboola.com/libtrc/static/thumbnails/a2b272be514ca3ebe3f97a4a32a41db5.jpg!-#@1600x1000","origin":"default","thumb-size":"1600x1000","title":"Get Roofing Services At Prices You Can Afford In Edmonton","type":"text","published-date":"1641997069","branding-text":"Roofing Services | Search Ads","url":"https://inneth-conded.xyz/9ad2e613-8777-4fe7-9a52-386c88879289?site\u003dwattpad-placement-255\u0026site_id\u003d1420260\u0026title\u003dGet+Roofing+Services+At+Prices+You+Can+Afford+In+Edmonton\u0026platform\u003dSmartphone\u0026campaign_id\u003d15573949\u0026campaign_item_id\u003d3108610633\u0026thumbnail\u003dhttp%3A%2F%2Fcdn.taboola.com%2Flibtrc%2Fstatic%2Fthumbnails%2Fa2b272be514ca3ebe3f97a4a32a41db5.jpg\u0026cpc\u003d{cpc}\u0026click_id\u003dGiCIypnAQogsMTFL3e_mPaVM2qLvK3KRU6LWzEMUgeB6piCit1Uox6CNr5v5n-x1\u0026tblci\u003dGiCIypnAQogsMTFL3e_mPaVM2qLvK3KRU6LWzEMUgeB6piCit1Uox6CNr5v5n-x1#tblciGiCIypnAQogsMTFL3e_mPaVM2qLvK3KRU6LWzEMUgeB6piCit1Uox6CNr5v5n-x1","duration":"0","sig":"328243c4127ff16e3fdcd7270bab908f6f3fc5b4c98d","item-id":"~~V1~~2785119550041083381~~PnBkfBE9JnQxpahv0adkcuIcmMhroRAHXwLZd-7zhunTxvAnL2wqac4MyzR7uD46gj3kUkbS3FhelBtnsiJV6MhkDZRZzzIqDobN6rWmCPA3hYz5D3PLat6nhIftiT1lwdxwdlxkeV_Mfb3eos_TQavImGhxk0e7psNAZxHJ9RKL2w3lppALGgQJoy2o6lkf-pOqODtX1VkgWpEEM4WsVoWOnUTAwdyGd-8yrze8CWNp752y28hl7lleicyO1vByRdbgwlJdnqyroTPEQNNEn1JRxBOSYSWt-Xm3vkPm-G4","uploader":"","is-syndicated":"true","publisher":"search","id":"~~V1~~2785119550041083381~~PnBkfBE9JnQxpahv0adkcuIcmMhroRAHXwLZd-7zhunTxvAnL2wqac4MyzR7uD46gj3kUkbS3FhelBtnsiJV6MhkDZRZzzIqDobN6rWmCPA3hYz5D3PLat6nhIftiT1lwdxwdlxkeV_Mfb3eos_TQavImGhxk0e7psNAZxHJ9RKL2w3lppALGgQJoy2o6lkf-pOqODtX1VkgWpEEM4WsVoWOnUTAwdyGd-8yrze8CWNp752y28hl7lleicyO1vByRdbgwlJdnqyroTPEQNNEn1JRxBOSYSWt-Xm3vkPm-G4","category":"home","views":"0","itp":[{"u":"https://trc.taboola.com/1326786/log/3/unip?en\u003dclickersusa","t":"c"}],"description":""}]}],"cpcud":{"upc":"0.0","upr":"0.0"}}}\n});\n\u003c/script\u003e\n\n\u003cscript type\u003d"text/javascript"\u003e\nwindow._taboola \u003d window._taboola || [];\n_taboola.push({flush: true});\n\u003c/script\u003e\n\n\u003c/body\u003e\n\u003c/html\u003e', + 'adomain': [ + 'example.xyz' + ], + 'cid': '15744349', + 'crid': '278195503434041083381', + 'w': 300, + 'h': 250, + 'lurl': 'http://us-trc.taboola.com/sample' + } + ], + 'seat': '14204545260' + } + ], + 'bidid': 'da43860a-4644-442a-b5e0-93f268cf8d19', + 'cur': 'USD' } }; @@ -335,28 +334,28 @@ describe('Taboola Adapter', function () { it('should return empty array if server response without seatbid', function () { const overriddenServerResponse = {...serverResponse}; - const seatbid = {...serverResponse.body.bidResponse.seatbid[0]}; - overriddenServerResponse.body.bidResponse.seatbid[0] = {}; + const seatbid = {...serverResponse.body.seatbid[0]}; + overriddenServerResponse.body.seatbid[0] = {}; const res = spec.interpretResponse(overriddenServerResponse, request) expect(res).to.be.an('array').that.is.empty - overriddenServerResponse.body.bidResponse.seatbid[0] = seatbid; + overriddenServerResponse.body.seatbid[0] = seatbid; }); it('should return empty array if server response without bids', function () { const overriddenServerResponse = {...serverResponse}; - const bid = [...serverResponse.body.bidResponse.seatbid[0].bid]; - overriddenServerResponse.body.bidResponse.seatbid[0].bid = {}; + const bid = [...serverResponse.body.seatbid[0].bid]; + overriddenServerResponse.body.seatbid[0].bid = {}; const res = spec.interpretResponse(overriddenServerResponse, request) expect(res).to.be.an('array').that.is.empty - overriddenServerResponse.body.bidResponse.seatbid[0].bid = bid; + overriddenServerResponse.body.seatbid[0].bid = bid; }); it('should interpret display response', function () { - const [bid] = serverResponse.body.bidResponse.seatbid[0].bid; + const [bid] = serverResponse.body.seatbid[0].bid; const expectedRes = [ { requestId: request.bids[0].bidId, @@ -364,7 +363,7 @@ describe('Taboola Adapter', function () { creativeId: bid.crid, ttl: 360, netRevenue: false, - currency: serverResponse.body.bidResponse.cur, + currency: serverResponse.body.cur, mediaType: 'banner', ad: bid.adm, width: bid.w, From 22cd7575de5c42666d4fba696665aba5ac7eb876 Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 9 May 2022 15:31:21 +0300 Subject: [PATCH 64/76] fix tests --- test/spec/modules/taboolaBidAdapter_spec.js | 30 +++++++++++++-------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index 86e32843228..070d6637aa2 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -4,6 +4,16 @@ import {config} from '../../../src/config' import * as utils from '../../../src/utils' describe('Taboola Adapter', function () { + let hasLocalStorage, cookiesAreEnabled, getDataFromLocalStorage, localStorageIsEnabled, getCookie; + + before(() => { + hasLocalStorage = sinon.stub(userData.storageManager, 'hasLocalStorage'); + cookiesAreEnabled = sinon.stub(userData.storageManager, 'cookiesAreEnabled'); + getCookie = sinon.stub(userData.storageManager, 'getCookie'); + getDataFromLocalStorage = sinon.stub(userData.storageManager, 'getDataFromLocalStorage'); + localStorageIsEnabled = sinon.stub(userData.storageManager, 'localStorageIsEnabled'); + }); + const commonBidRequest = { bidder: 'taboola', params: { @@ -196,8 +206,9 @@ describe('Taboola Adapter', function () { describe('handle userid ', function () { it('should get user id from local storage', function () { - const getDataFromLocalStorage = sinon.stub(userData.storageManager, 'getDataFromLocalStorage'); getDataFromLocalStorage.returns(51525152); + hasLocalStorage.returns(true); + localStorageIsEnabled.returns(true); const bidderRequest = { ...commonBidderRequest, @@ -206,16 +217,17 @@ describe('Taboola Adapter', function () { const res = spec.buildRequests([defaultBidRequest], bidderRequest); const resData = JSON.parse(res.data); expect(resData.user.buyeruid).to.equal(51525152); + getDataFromLocalStorage.restore(); + hasLocalStorage.restore(); + localStorageIsEnabled.restore(); }); it('should get user id from cookie if local storage isn`t defined', function () { - const hasLocalStorage = sinon.stub(userData.storageManager, 'hasLocalStorage'); - const getDataFromLocalStorage = sinon.stub(userData.storageManager, 'getDataFromLocalStorage'); - const getCookie = sinon.stub(userData.storageManager, 'getCookie'); - getDataFromLocalStorage.returns(51525152); hasLocalStorage.returns(false); + localStorageIsEnabled.returns(false); + cookiesAreEnabled.returns(true); getCookie.returns('taboola%20global%3Auser-id=12121212'); const bidderRequest = { @@ -223,17 +235,16 @@ describe('Taboola Adapter', function () { }; const res = spec.buildRequests([defaultBidRequest], bidderRequest); const resData = JSON.parse(res.data); + expect(resData.user.buyeruid).to.equal('12121212'); getDataFromLocalStorage.restore(); hasLocalStorage.restore(); + cookiesAreEnabled.restore(); getCookie.restore(); }); it('should get user id from TRC if local storage and cookie isn`t defined', function () { - const hasLocalStorage = sinon.stub(userData.storageManager, 'hasLocalStorage'); - const cookiesAreEnabled = sinon.stub(userData.storageManager, 'cookiesAreEnabled'); - hasLocalStorage.returns(false); cookiesAreEnabled.returns(false); @@ -254,9 +265,6 @@ describe('Taboola Adapter', function () { }); it('should get user id to be 0 if cookie, local storage, TRC isn`t defined', function () { - const hasLocalStorage = sinon.stub(userData.storageManager, 'hasLocalStorage'); - const cookiesAreEnabled = sinon.stub(userData.storageManager, 'cookiesAreEnabled'); - hasLocalStorage.returns(false); cookiesAreEnabled.returns(false); From 6963eb0866055b5f1ea335078d3d89378deaf974 Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 9 May 2022 21:34:44 +0300 Subject: [PATCH 65/76] fix tests - run stubs on each test --- test/spec/modules/taboolaBidAdapter_spec.js | 33 +++++++++------------ 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index 070d6637aa2..f1522e72fa9 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -6,7 +6,7 @@ import * as utils from '../../../src/utils' describe('Taboola Adapter', function () { let hasLocalStorage, cookiesAreEnabled, getDataFromLocalStorage, localStorageIsEnabled, getCookie; - before(() => { + beforeEach(() => { hasLocalStorage = sinon.stub(userData.storageManager, 'hasLocalStorage'); cookiesAreEnabled = sinon.stub(userData.storageManager, 'cookiesAreEnabled'); getCookie = sinon.stub(userData.storageManager, 'getCookie'); @@ -14,6 +14,14 @@ describe('Taboola Adapter', function () { localStorageIsEnabled = sinon.stub(userData.storageManager, 'localStorageIsEnabled'); }); + afterEach(() => { + hasLocalStorage.restore(); + cookiesAreEnabled.restore(); + getCookie.restore(); + getDataFromLocalStorage.restore(); + localStorageIsEnabled.restore(); + }) + const commonBidRequest = { bidder: 'taboola', params: { @@ -114,7 +122,7 @@ describe('Taboola Adapter', function () { 'page': commonBidderRequest.refererInfo.canonicalUrl, 'ref': commonBidderRequest.refererInfo.referer, 'publisher': {'id': commonBidRequest.params.publisherId}, - 'content': {'language': 'en-US'} + 'content': {'language': navigator.language} }, 'device': {'ua': navigator.userAgent}, 'source': {'fd': 1}, @@ -127,10 +135,10 @@ describe('Taboola Adapter', function () { 'regs': {'coppa': 0, 'ext': {}} }; - const res = spec.buildRequests([defaultBidRequest], commonBidderRequest) + const res = spec.buildRequests([defaultBidRequest], commonBidderRequest); - expect(res.url).to.equal(`${END_POINT_URL}/${commonBidRequest.params.publisherId}`) - expect(res.data).to.deep.equal(JSON.stringify(expectedData)) + expect(res.url).to.equal(`${END_POINT_URL}/${commonBidRequest.params.publisherId}`); + expect(res.data).to.deep.equal(JSON.stringify(expectedData)); }) it('should pass optional parameters in request', function () { @@ -217,10 +225,6 @@ describe('Taboola Adapter', function () { const res = spec.buildRequests([defaultBidRequest], bidderRequest); const resData = JSON.parse(res.data); expect(resData.user.buyeruid).to.equal(51525152); - - getDataFromLocalStorage.restore(); - hasLocalStorage.restore(); - localStorageIsEnabled.restore(); }); it('should get user id from cookie if local storage isn`t defined', function () { @@ -237,16 +241,12 @@ describe('Taboola Adapter', function () { const resData = JSON.parse(res.data); expect(resData.user.buyeruid).to.equal('12121212'); - - getDataFromLocalStorage.restore(); - hasLocalStorage.restore(); - cookiesAreEnabled.restore(); - getCookie.restore(); }); it('should get user id from TRC if local storage and cookie isn`t defined', function () { hasLocalStorage.returns(false); cookiesAreEnabled.returns(false); + localStorageIsEnabled.returns(false); window.TRC = { user_id: 31313131 @@ -259,8 +259,6 @@ describe('Taboola Adapter', function () { const resData = JSON.parse(res.data); expect(resData.user.buyeruid).to.equal(31313131); - hasLocalStorage.restore(); - cookiesAreEnabled.restore(); delete window.TRC; }); @@ -274,9 +272,6 @@ describe('Taboola Adapter', function () { const res = spec.buildRequests([defaultBidRequest], bidderRequest); const resData = JSON.parse(res.data); expect(resData.user.buyeruid).to.equal(0); - - hasLocalStorage.restore(); - cookiesAreEnabled.restore(); }); it('should set buyeruid to be 0 if it`s a new user', function () { From 17570416868d2336e33508f446c4bed13999ad25 Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 9 May 2022 21:45:38 +0300 Subject: [PATCH 66/76] rerun because of another adapter flaky test --- test/spec/modules/taboolaBidAdapter_spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index f1522e72fa9..7f1b1feb1f0 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -257,7 +257,7 @@ describe('Taboola Adapter', function () { } const res = spec.buildRequests([defaultBidRequest], bidderRequest); const resData = JSON.parse(res.data); - expect(resData.user.buyeruid).to.equal(31313131); + expect(resData.user.buyeruid).to.equal(window.TRC.user_id); delete window.TRC; }); From 9229d6b0642bb97a39af75f8be0119bad6a4feb7 Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 9 May 2022 21:59:01 +0300 Subject: [PATCH 67/76] rerun because of another adapter flaky test --- test/spec/modules/taboolaBidAdapter_spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index 7f1b1feb1f0..994b3c2e35f 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -249,7 +249,7 @@ describe('Taboola Adapter', function () { localStorageIsEnabled.returns(false); window.TRC = { - user_id: 31313131 + user_id: 31313132 }; const bidderRequest = { From 0653addffa75d37d59502bb342a607c624136fbd Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 10 May 2022 16:23:31 +0300 Subject: [PATCH 68/76] fix cors issue, switch between height, width position --- modules/taboolaBidAdapter.js | 9 ++++++--- test/spec/modules/taboolaBidAdapter_spec.js | 8 ++++---- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js index cb6a6048d7f..578a94039fe 100644 --- a/modules/taboolaBidAdapter.js +++ b/modules/taboolaBidAdapter.js @@ -145,7 +145,10 @@ export const spec = { url, method: 'POST', data: JSON.stringify(request), - bids: validBidRequests + bids: validBidRequests, + options: { + withCredentials: false + }, }; }, interpretResponse: (serverResponse, {bids}) => { @@ -202,8 +205,8 @@ function getSizes(sizes) { return { format: sizes.map(size => { return { - h: size[0], - w: size[1] + w: size[0], + h: size[1] } }) } diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index 994b3c2e35f..39dd1f9105b 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -102,12 +102,12 @@ describe('Taboola Adapter', function () { 'id': 1, 'banner': { format: [{ - h: displayBidRequestParams.sizes[0][0], - w: displayBidRequestParams.sizes[0][1] + w: displayBidRequestParams.sizes[0][0], + h: displayBidRequestParams.sizes[0][1] }, { - h: displayBidRequestParams.sizes[1][0], - w: displayBidRequestParams.sizes[1][1] + w: displayBidRequestParams.sizes[1][0], + h: displayBidRequestParams.sizes[1][1] } ] }, From 040b74710f52c6fe7e335bbecc732518475f7bb5 Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 22 May 2022 09:31:34 +0300 Subject: [PATCH 69/76] update badv, bcat to be based in the ortb2 to support prebid 7 new protocols + update Ut --- modules/taboolaBidAdapter.js | 11 ++++++++--- test/spec/modules/taboolaBidAdapter_spec.js | 9 +++++++-- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js index 578a94039fe..f93dd39a484 100644 --- a/modules/taboolaBidAdapter.js +++ b/modules/taboolaBidAdapter.js @@ -100,7 +100,7 @@ export const spec = { buildRequests: (validBidRequests, bidderRequest) => { const [bidRequest] = validBidRequests; const {refererInfo, gdprConsent = {}, uspConsent} = bidderRequest; - const {bcat = [], badv = [], publisherId} = bidRequest.params; + const {publisherId} = bidRequest.params; const site = getSiteProperties(bidRequest.params, refererInfo); const device = {ua: navigator.userAgent}; const imps = getImps(validBidRequests); @@ -126,6 +126,11 @@ export const spec = { regs.coppa = 1 } + const ortb2 = config.getConfig('ortb2') || { + badv: [], + bcat: [] + }; + const request = { id: bidderRequest.auctionId, imp: imps, @@ -133,8 +138,8 @@ export const spec = { device, source: {fd: 1}, tmax: bidderRequest.timeout, - bcat, - badv, + bcat: ortb2.bcat, + badv: ortb2.badv, user, regs }; diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index 39dd1f9105b..cbc6b4cd6fd 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -143,11 +143,16 @@ describe('Taboola Adapter', function () { it('should pass optional parameters in request', function () { const optionalParams = { - badv: ['adadadbcd.com'], - bcat: ['IAB25', 'IAB7-39'], + bidfloor: 0.25, bidfloorcur: 'EUR' } + + config.setConfig({ortb2: { + badv: ['adadadbcd.com'], + bcat: ['IAB25', 'IAB7-39'] + }}); + const bidRequest = { ...defaultBidRequest, params: {...commonBidRequest.params, ...optionalParams} From c99fbbb627f67ecb1d79212588058ff9ffd48a36 Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 22 May 2022 09:44:06 +0300 Subject: [PATCH 70/76] retry run circleci --- test/spec/modules/taboolaBidAdapter_spec.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index cbc6b4cd6fd..795a436949d 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -143,7 +143,6 @@ describe('Taboola Adapter', function () { it('should pass optional parameters in request', function () { const optionalParams = { - bidfloor: 0.25, bidfloorcur: 'EUR' } From c39b6cf7906a48501405542fc43479e00a238d7c Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 22 May 2022 09:53:13 +0300 Subject: [PATCH 71/76] retry run circleci --- test/spec/modules/taboolaBidAdapter_spec.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index 795a436949d..58e872aabb4 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -145,7 +145,7 @@ describe('Taboola Adapter', function () { const optionalParams = { bidfloor: 0.25, bidfloorcur: 'EUR' - } + }; config.setConfig({ortb2: { badv: ['adadadbcd.com'], @@ -155,14 +155,14 @@ describe('Taboola Adapter', function () { const bidRequest = { ...defaultBidRequest, params: {...commonBidRequest.params, ...optionalParams} - } + }; - const res = spec.buildRequests([bidRequest], commonBidderRequest) - const resData = JSON.parse(res.data) - expect(resData.bcat).to.deep.equal(['IAB25', 'IAB7-39']) - expect(resData.badv).to.deep.equal(['adadadbcd.com']) - expect(resData.imp[0].bidfloor).to.deep.equal(0.25) - expect(resData.imp[0].bidfloorcur).to.deep.equal('EUR') + const res = spec.buildRequests([bidRequest], commonBidderRequest); + const resData = JSON.parse(res.data); + expect(resData.bcat).to.deep.equal(['IAB25', 'IAB7-39']); + expect(resData.badv).to.deep.equal(['adadadbcd.com']); + expect(resData.imp[0].bidfloor).to.deep.equal(0.25); + expect(resData.imp[0].bidfloorcur).to.deep.equal('EUR'); }); it('should pass bidder timeout', function () { From 0cdb2934c1e67e46ac2c6262d61f858c078c24aa Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 22 May 2022 10:04:47 +0300 Subject: [PATCH 72/76] pull from upstream update md (placement + pub ) --- modules/taboolaBidAdapter.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/taboolaBidAdapter.md b/modules/taboolaBidAdapter.md index a3cd1d9261b..a4213355049 100644 --- a/modules/taboolaBidAdapter.md +++ b/modules/taboolaBidAdapter.md @@ -26,8 +26,8 @@ The Taboola Bidding adapter requires setup before beginning. Please contact us o bids: [{ bidder: 'taboola', params: { - tagId: 'Placement Name', - publisherId: 'your-publisher-id', + tagId: 'tester-placement', // Placement Name + publisherId: 'tester-pub', // your-publisher-id bidfloor: 0.25, // Optional - default is null bcat: ['IAB1-1'], // Optional - default is [] badv: ['example.com'] // Optional - default is [] From 1c714039c8b55e324289b7f03fe5ac9769d8cc69 Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 22 May 2022 11:59:43 +0300 Subject: [PATCH 73/76] update badv, bcat UT --- test/spec/modules/taboolaBidAdapter_spec.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index 58e872aabb4..d3b22b7291c 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -147,11 +147,6 @@ describe('Taboola Adapter', function () { bidfloorcur: 'EUR' }; - config.setConfig({ortb2: { - badv: ['adadadbcd.com'], - bcat: ['IAB25', 'IAB7-39'] - }}); - const bidRequest = { ...defaultBidRequest, params: {...commonBidRequest.params, ...optionalParams} @@ -159,8 +154,6 @@ describe('Taboola Adapter', function () { const res = spec.buildRequests([bidRequest], commonBidderRequest); const resData = JSON.parse(res.data); - expect(resData.bcat).to.deep.equal(['IAB25', 'IAB7-39']); - expect(resData.badv).to.deep.equal(['adadadbcd.com']); expect(resData.imp[0].bidfloor).to.deep.equal(0.25); expect(resData.imp[0].bidfloorcur).to.deep.equal('EUR'); }); From 10acfe702fc8f6cc8e650f7fb9f02856a9f9b894 Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 23 May 2022 14:02:24 +0300 Subject: [PATCH 74/76] rerun build --- test/spec/modules/taboolaBidAdapter_spec.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index d3b22b7291c..998bf2ef560 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -163,9 +163,9 @@ describe('Taboola Adapter', function () { ...commonBidderRequest, timeout: 500 } - const res = spec.buildRequests([defaultBidRequest], bidderRequest) - const resData = JSON.parse(res.data) - expect(resData.tmax).to.equal(500) + const res = spec.buildRequests([defaultBidRequest], bidderRequest); + const resData = JSON.parse(res.data); + expect(resData.tmax).to.equal(500); }); describe('handle privacy segments when building request', function () { From c67302eca342af1ab15ec0e4e968e599ff4e1499 Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 23 May 2022 14:41:11 +0300 Subject: [PATCH 75/76] rerun build --- test/spec/modules/taboolaBidAdapter_spec.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index 998bf2ef560..30553bd9190 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -193,9 +193,9 @@ describe('Taboola Adapter', function () { }, uspConsent: 'consentString' } - const res = spec.buildRequests([defaultBidRequest], bidderRequest) - const resData = JSON.parse(res.data) - expect(resData.regs.ext.us_privacy).to.equal('consentString') + const res = spec.buildRequests([defaultBidRequest], bidderRequest); + const resData = JSON.parse(res.data); + expect(resData.regs.ext.us_privacy).to.equal('consentString'); }); it('should pass coppa consent', function () { From 07843638c4505df12b0b9cd88e03228c20de510f Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 26 May 2022 18:46:16 +0300 Subject: [PATCH 76/76] support storageAllowed restriction on unit tests for prebid 7 --- test/spec/modules/taboolaBidAdapter_spec.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index 30553bd9190..1178d4062c4 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -12,6 +12,12 @@ describe('Taboola Adapter', function () { getCookie = sinon.stub(userData.storageManager, 'getCookie'); getDataFromLocalStorage = sinon.stub(userData.storageManager, 'getDataFromLocalStorage'); localStorageIsEnabled = sinon.stub(userData.storageManager, 'localStorageIsEnabled'); + + $$PREBID_GLOBAL$$.bidderSettings = { + taboola: { + storageAllowed: true + } + }; }); afterEach(() => {